Makefile 将与不同子文件夹中的源匹配的文件生成到单个生成文件夹中

Makefile 将与不同子文件夹中的源匹配的文件生成到单个生成文件夹中,makefile,gnu-make,avr-gcc,Makefile,Gnu Make,Avr Gcc,我想创建一个make文件,它获取几个src子目录中的所有文件,并将它们直接编译到一个构建目录中 例如:我有 src/main.c src/i2c/i2c.c src/i2c/i2c.h 作为输出,我需要对象文件以及最终的二进制文件 -build/main.o -build/i2c.o -build/release.elf 我设法将所有源文件作为一个列表及其各自的子目录路径放入一个变量中,我还设法获得所有输出文件的列表,但当我试图创建一个目标以在该生成目录中生成所有.o文件时,它与相应的.c文

我想创建一个make文件,它获取几个src子目录中的所有文件,并将它们直接编译到一个构建目录中

例如:我有

  • src/main.c
  • src/i2c/i2c.c
  • src/i2c/i2c.h
作为输出,我需要对象文件以及最终的二进制文件 -build/main.o -build/i2c.o -build/release.elf

我设法将所有源文件作为一个列表及其各自的子目录路径放入一个变量中,我还设法获得所有输出文件的列表,但当我试图创建一个目标以在该生成目录中生成所有.o文件时,它与相应的.c文件和.o文件不匹配。在这里,我只是不知道如何将这两者联系起来

尝试将main.o与i2c.c匹配时失败

以下是Makefile的“相关”部分:

TARGET = $(lastword $(subst /, ,$(CURDIR)))

BUILD_DIR  := buildDir

SOURCES = $(wildcard src/*.c src/*/*.c)
BROKENOBJECTS = $(SOURCES:.c=.o)
LESSBROKEN = $(notdir $(BROKENOBJECTS))
OBJECT_FILES = $(addprefix $(BUILD_DIR)/, $(LESSBROKEN))

$(BUILD_DIR)/%.o: $(SOURCES) $(BUILD_DIR)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

$(BUILD_DIR)/$(TARGET).elf: $(OBJECT_FILES)
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@

$(BUILD_DIR) :
    mkdir -p $@

compile : $(BUILD_DIR)/$(TARGET).elf
TARGET=$(lastword$(subst/,$(CURDIR)))
BUILD_DIR:=buildDir
SOURCES=$(通配符src/*.c src/*/*.c)
BROKENOBJECTS=$(来源:.c=.o)
LESSBROKEN=$(notdir$(BrokerNobjects))
OBJECT_FILES=$(addprefix$(BUILD_DIR)/,$(LESSBROKEN))
$(构建目录)/%.o:$(源代码)$(构建目录)
$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-c-o$@$<
$(构建目录)/$(目标).elf:$(对象文件)
$(CC)$(LDFLAGS)$(TARGET_ARCH)$^$(LDLIBS)-o$@
$(构建目录):
mkdir-p$@
编译:$(BUILD\u DIR)/$(TARGET).elf

我该怎么做呢?从$(SOURCES)运行每个.c文件的配方,然后在buildDir/?

中创建相应的.o文件。下面是一个修改过的代码片段,它可以做您想做的事情,但是如果不在
src/
中手动指定每个子目录,我就找不到解决方案

SOURCES = $(wildcard src/*.c)
SUBSOURCES = $(wildcard src/*/*.c)

OBJECTS = $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCES:.c=.o)))
SUBOBJECTS = $(addprefix $(BUILD_DIR)/, $(notdir $(SUBSOURCES:.c=.o)))

compile : $(BUILD_DIR)/$(TARGET).elf

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) $(SUBOBJECTS)
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@

# save some typing for the rules below
COMPILE = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

$(OBJECTS): $(BUILD_DIR)/%.o: src/%.c | $(BUILD_DIR)
    $(COMPILE)

$(BUILD_DIR)/%.o: src/i2c/%.c | $(BUILD_DIR)
    $(COMPILE)

$(BUILD_DIR)/%.o: src/someOtherSubdir/%.c | $(BUILD_DIR)
    $(COMPILE)
SOURCES=$(通配符src/*.c)
子资源=$(通配符src/*/*.c)
OBJECTS=$(addprefix$(BUILD_DIR)/,$(notdir$(SOURCES:.c=.o)))
子对象=$(addprefix$(BUILD_DIR)/,$(notdir$(子源:.c=.o)))
编译:$(BUILD\u DIR)/$(TARGET).elf
$(构建目录)/$(目标).elf:$(对象)$(子对象)
$(CC)$(LDFLAGS)$(TARGET_ARCH)$^$(LDLIBS)-o$@
#为下面的规则保存一些键入内容
COMPILE=$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-c-o$@$<
$(对象):$(构建目录)/%.o:src/%.c |$(构建目录)
$(编译)
$(构建目录)/%.o:src/i2c/%.c$(构建目录)
$(编译)
$(BUILD_DIR)/%.o:src/someOtherSubdir/%.c |$(BUILD_DIR)
$(编译)

正如@G.M.在注释中所建议的,您必须确保源文件名在子目录中是唯一的。还请注意,我将
$(BUILD\u DIR)
转换为一个,它应该更准确地反映您的意图。

您可以使用
make
。因此,与其使用指定可能的源路径,不如

SOURCES = $(wildcard src/*.c src/*/*.c)
你会

# Build a list of directories under src
#
SOURCE_DIRS := $(shell find src -type d)

# Use the list in $(SOURCE_DIRS) as a search path for .c files.
#
vpath %.c $(SOURCE_DIRS)
现在,当尝试更新i2c.o时(例如),规则

$(BUILD_DIR)/%.o: %.c $(BUILD_DIR)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
$(构建目录)/%.o:%.c$(构建目录)
$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-c-o$@$<
将导致
make
在源目录列表中自动搜索依赖项
i2c.c



注意:由于明显的原因,不同源目录下具有相同名称的多个文件将导致此处出现问题。因此,我最初的问题(在评论中)是关于不同目录下源文件名的唯一性。

假设您使用GNU make,并且您的C源文件都是
*.C
,可以在当前目录及其所有子目录(任何深度)中找到,这应该接近您想要的:

BUILDDIR := build
SRC      := $(shell find . -type f -name '*.c')

# Convert C source file name(s) to object file name(s)
# $(1): C source file name(s)
define c2o
$(patsubst %.c,$(BUILDDIR)/%.o,$(notdir $(1)))
endef

OBJ := $(call c2o,$(SRC))

.PHONY: all

all: $(OBJ)

# Compilation rule for a C source file (use echo for testing)
# $(1): C source file name
define MY_rule
$$(call c2o,$(1)): $(1)
    @echo $$(CC) $$(CFLAGS) $$(CPPFLAGS) $$(TARGET_ARCH) -c -o $$@ $$<
endef

# Instantiate compilation rules for all C source files
$(foreach s,$(SRC),$(eval $(call MY_rule,$(s))))
注意在
MY\u规则
的定义中使用了
$
。之所以需要它,是因为它被扩展了两次:一次是在扩展
eval
函数的参数时,另一次是在make将结果解析为常规make语法时

正如在其他注释和答案中所解释的那样,只有当您没有多个具有相同基名称的C源文件时,这才有效。有一种方法可以检测这种情况,并在遇到错误时发出错误。make
sort
函数对其单词列表参数进行排序,但也会删除重复项。因此,如果排序前后的字数不同,则存在重复。在
OBJ
的定义之后添加以下内容:

SOBJ     := $(sort $(OBJ))

ifneq ($(words $(OBJ)),$(words $(SOBJ)))
$(error Found multiple C source files with same base name)
endif
演示:


您所有的
*.c
文件都有唯一的名称吗?i、 e.如果目录
src/i2c
中有一个文件
i2c.c
,那么其他源目录将不会有名为
i2c.c
的文件。目前不会。在遥远的将来,我可能会遇到这样一种情况,我希望将不同的实现与之链接,但在这种情况下,我认为我应该“只是”在其中包含一个不同的源文件夹和特定的实现。或者专门添加该源文件。目前,所有现存的东西都应该包括在内。我担心目前的情况就是这样。在扩展我的程序时,我特别尝试避免添加每个新的子文件夹。感觉有点…不方便。也许其他人会想出解决办法?!虽然你也可以把这一不便看作是一种暗示,不同的方法可能会更好?:)没有任何错误,例如,您的
$(BUILD_DIR)
中的子文件夹与
src/
中的子文件夹相同。我确实看到了这一点,但示例似乎更复杂,我认为这不值得。如果在build文件夹中创建了相同的文件夹结构,那么实际情况又会如何呢?我努力创建相应的子目录。现在我不能马上告诉你。可能是它增加了生成文件的复杂性。您是否有如此多的子目录,以至于在上面的代码片段中列出它们是一件非常痛苦的事情?到目前为止,这似乎完全按照我的预期解决了我的问题。真是聪明的事。我想提高这一点,但还没有足够的声誉这样做。。。谢谢你的回答!不完全是。
vpath
指令告诉
SOBJ     := $(sort $(OBJ))

ifneq ($(words $(OBJ)),$(words $(SOBJ)))
$(error Found multiple C source files with same base name)
endif
host> touch c/c/a.c
host> make
Makefile:13: *** Found multiple C source files with same base name.  Stop.