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)
?
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 (make
d). 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 $@