如何在Makefile中用`%.o: %.c`让avr-gcc将*.o输出到独立文件夹?
解决AVR项目Makefile编译产物目录问题
核心问题分析
你的需求是将所有编译产物(.o、.elf、*.hex)统一放到build目录,但之前的尝试失败主要有两个原因:
OBJECTS变量直接把源文件路径替换成.o,导致Make期望的.o文件和源文件同目录,而非build目录- 模式规则中直接使用
build/$@时,会尝试在build下创建和源文件相同的子目录(比如build/src/xxx.o),但该子目录不存在,导致编译失败
修正后的完整Makefile
# My whole Makefile MCU = atmega328p F_CPU = 1000000UL BAUD = 9600UL LIBDIR = ~/Developer/bin/avr8-gnu-toolchain-darwin_x86_64/avr/include PROGRAMMER_TYPE = usbtiny PROGRAMMER_ARGS = CC = avr-gcc OBJCOPY = avr-objcopy OBJDUMP = avr-objdump AVRSIZE = avr-size AVRDUDE = avrdude ##########------------------------------------------------------########## # 确保build目录存在 BUILD_DIR = build $(BUILD_DIR): mkdir -p $@ TARGET = $(lastword $(subst /, ,$(CURDIR))) # 收集所有源文件(根目录和src目录,排除库目录源文件避免不必要编译) SOURCES = $(wildcard *.c src/*.c) # 将所有源文件转换为build目录下的.o文件:根目录xxx.c→build/xxx.o;src/xxx.c→build/xxx.o OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SOURCES))) # 收集所有头文件 HEADERS = $(wildcard *.h src/*.h) CPPFLAGS = -DF_CPU=$(F_CPU) -DBAUD=$(BAUD) -I. -Isrc -I$(LIBDIR) CFLAGS = -Os -g -std=gnu99 -Wall CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums CFLAGS += -ffunction-sections -fdata-sections LDFLAGS = -Wl,-Map,$(BUILD_DIR)/$(TARGET).map LDFLAGS += -Wl,--gc-sections TARGET_ARCH = -mmcu=$(MCU) # 模式规则:处理根目录的.c文件,生成到build目录 $(BUILD_DIR)/%.o: %.c $(HEADERS) | $(BUILD_DIR) $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ # 模式规则:处理src目录的.c文件,生成到build目录 $(BUILD_DIR)/%.o: src/%.c $(HEADERS) | $(BUILD_DIR) $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ $(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@ $(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf $(OBJCOPY) -j .text -j .data -O ihex $< $@ all: $(BUILD_DIR)/$(TARGET).hex size: $(BUILD_DIR)/$(TARGET).elf $(AVRSIZE) -C --mcu=$(MCU) $< clean: rm -rf $(BUILD_DIR) flash: $(BUILD_DIR)/$(TARGET).hex $(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U flash:w:$<
关键修改点说明
统一产物路径:
- 使用
$(notdir $(SOURCES))去掉源文件的目录前缀,再通过patsubst转换为build目录下的.o文件,确保所有.o都在build根目录下 - 新增
BUILD_DIR变量,所有编译产物路径都基于该变量,避免硬编码
- 使用
修复模式规则:
- 新增两条模式规则,分别处理根目录和
src目录的源文件,直接生成到build目录 - 使用
| $(BUILD_DIR)作为顺序依赖,确保编译前先创建build目录
- 新增两条模式规则,分别处理根目录和
优化头文件依赖:
- 明确收集根目录和
src目录的头文件,同时在CPPFLAGS中添加-Isrc,让编译器能找到src目录下的头文件
- 明确收集根目录和
简化clean命令:
- 直接删除整个
build目录,避免遗漏编译产物
- 直接删除整个
之前方案的错误解析
- 方案1:
build/$@中的$@包含源文件的目录(比如src/RotaryEncoder.o),导致尝试创建build/src/RotaryEncoder.o,但build/src目录不存在,编译失败 - 方案2:
$(HEADERS)变量包含了库目录的头文件,可能导致Make认为目标已经是最新状态,跳过编译步骤,后续的mv和echo自然不会执行 - 方案3:
mv $@ build会把src/RotaryEncoder.o移动到build/src/RotaryEncoder.o,但build/src目录不存在,所以报错;同时$^会把所有依赖(包括头文件)传给编译器,可能引发编译错误
内容的提问来源于stack exchange,提问作者user19681631
相关产品推荐
相关产品推荐

