Search code examples
c++cmakefilegnu-make

Makefile results in multiple definition errors


I have the following Makefile:

TARGET = main.elf
BUILD_DIR = build
SRC_DIR = src

CC = arm-none-eabi-gcc -std=gnu17
CXX = arm-none-eabi-g++ -std=c++17 
LINKER = arm-none-eabi-g++
OBJDUMP = arm-none-eabi-objdump
OBJCOPY = arm-none-eabi-objcopy

INCLUDES = -I./src/inc \
           -I./src/usys/inc

SRC_FILES_C =   $(SRC_DIR)/usys/startup.c \
                $(SRC_DIR)/usys/usys.c

SRC_FILES_CXX = $(SRC_DIR)/main.cpp

LINKER_FILE = $(SRC_DIR)/usys/flash.ld

CFLAGS = -Wall -Wextra -g3 -O0
CFLAGS += -c -mthumb -fdata-sections -ffunction-sections -DARM
CFLAGS += -mcpu=cortex-m33 -mfpu=fpv5-sp-d16 -mfloat-abi=hard --specs=nano.specs

LFLAGS = -mcpu=cortex-m33 --specs=nosys.specs --specs=nano.specs
LFLAGS += -T$(LINKER_FILE) -Wl,-Map="$(TARGET).map" -Wl,--gc-sections -static
LFLAGS += -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group

C_OBJS = ${subst $(SRC_DIR),$(BUILD_DIR),$(SRC_FILES_C:.c=.o)}
CXX_OBJS = ${subst $(SRC_DIR),$(BUILD_DIR),$(SRC_FILES_CXX:.cpp=.o)}

ALL_OBJS = $(CXX_OBJS) $(C_OBJS)

.PHONY: clean

all: $(TARGET)

# Compile
$(C_OBJS): $(SRC_FILES_C)
    $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@

$(CXX_OBJS): $(SRC_FILES_CXX)
    $(CXX) $(INCLUDES) $(CFLAGS) -c $< -o $@

# Link
$(TARGET): $(ALL_OBJS)
    $(CXX) $(LFLAGS) $(ALL_OBJS) -o $@

# Clean
clean:
    @rm -f $(ALL_OBJS) $(TARGET)

My source directly roughly looks like this:

├───build
│   └───usys
├───src
|   main.cpp
│   └───usys
|       startup.c
|       usys.c
│       └───inc
└───tools

When I compile my program, I get multiple definition errors when linking. Which makes sense, because during compilation, I see the following:

arm-none-eabi-gcc -std=gnu17 -I./src/inc -I./src/usys/inc -Wall -Wextra -g3 -O0 -c -mthumb -fdata-sections -ffunction-sections -DARM -mcpu=cortex-m33 -mfpu=fpv5-sp-d16 -mfloat-abi=hard --specs=nano.specs -c src/usys/startup.c -o build/usys/usys.o

How can I modify my Makefile so that it does what I want: take the list of $(SRC_FILES_C), and creates the objects in the output at $(C_OBJS)?


Solution

  • Here's the problem:

    $(C_OBJS): $(SRC_FILES_C)
        $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@
    

    $(SRC_FILES_C) expands to a list of all the source files, and $< subsequently takes the first of these, which is src/usys/startup.c. This file is used as the input for each object file.

    Instead, you're looking for a pattern rule. A pattern rule is only applied if all prerequisites already exist or can be made (maked). Thanks to this, you can use pattern rules for both .c and .cpp files:

    $(BUILD_DIR)/%.o : $(SRC_DIR)/%.c
        $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@
    
    $(BUILD_DIR)/%.o : $(SRC_DIR)/%.cpp
        $(CXX) $(INCLUDES) $(CFLAGS) -c $< -o $@