make 变量的第二次扩展

second expansion of make variables

我有一个如下所示的生成文件。当前目录中有一些文件说 x.c,x.h。此外,我正在从目标 code_gen 下的 python 脚本生成一些 source/header 文件。生成后,我构建它们。

生成文件后,我不想使用 make all 再次生成它们。仅当缺少一个或多个生成的文件时,我才会生成。

#predefine list of generated src files.
GEN_SRC_FILES := a.c b.c d.c
GEN_HEADER_FILES := $(patsubst %.c,%.h,$(GEN_SRC_FILES))
GEN_FILES_LIST += $(GEN_SRC_FILES) $(GEN_HEADER_FILES)

#Get all source and header currently in dir.
SRC := $(wildcard *.c)
HEADER := $(wildcard *.h)

#Get list of generated source and header files currently existing in dir.
GEN_SRC_FILES_TEMP := $(filter $(GEN_SRC_FILES), $(SRC))
GEN_HEADER_FILES_TEMP := $(filter $(GEN_HEADER_FILES), $(HEADER))
GEN_FILES_LIST_TEMP += $(GEN_SRC_FILES_TEMP) $(GEN_HEADER_FILES_TEMP)

OBJECT_FILES :=$(patsubst %.c, %.o, $(SRC))
OBJ := $(patsubst %, $(BUILD_DIR)/%, $(OBJECT_FILES))
CLEAN_OBJECTS := $(OBJ)

ifneq ($(GEN_FILES_LIST_TEMP),$(GEN_FILES_LIST))
all:code_gen lib
else
all:lib
endif
$(BUILD_DIR)/%.o: %.c
        $(CC) -c $^ -o $@ $(CFLAGS) -I$(INCLUDE_PATH)

lib: $(BUILD_DIR)/lib_x.a

$(BUILD_DIR)/lib_x.a: $(OBJ)
        $(AR) $(ARCHIVE_OPTIONS) $(BUILD_DIR)/lib_x.a $(OBJ)

code_gen:
        python ../../my_script.py

第一次生成文件时出现问题:

SRC := $(wildcard *.c)
HEADER := $(wildcard *.h)

这只有现有的 .c/.h 文件 - x.c /x.h 在第一次调用 make 期间。所以当 code_gen 目标完成时

OBJECT_FILES :=$(patsubst %.c, %.o, $(SRC))
OBJ := $(patsubst %, $(BUILD_DIR)/%, $(OBJECT_FILES))

这仅指向 x.o,但现在有新文件 a.c,b.c,d.cobj 理想情况下应该是 x.o a.o b.o d.o。 如何强制 SRC,OBJ 的第二次扩展?
我试过 .SECONDEXPANSION 但无法得到它的语法正确。还有其他方法吗?
有什么建议吗?

你可以让 make 以自然的方式完成它的工作,即发现一些 C 文件丢失并告诉它如何生成它们(但也请阅读下面的重要说明):

GEN_SRC_FILES    := a.c b.c d.c
GEN_HEADER_FILES := $(patsubst %.c,%.h,$(GEN_SRC_FILES))
SRC              := $(sort $(wildcard *.c) $(GEN_SRC_FILES))
OBJ              := $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRC))

.PRECIOUS: $(GEN_SRC_FILES) $(GEN_HEADER_FILES)

all: lib

$(BUILD_DIR)/%.o: %.c %.h
    $(CC) -c $< -o $@ $(CFLAGS) -I$(INCLUDE_PATH)

lib: $(BUILD_DIR)/lib_x.a

$(BUILD_DIR)/lib_x.a: $(OBJ)
    $(AR) $(ARCHIVE_OPTIONS) $@ $^

$(GEN_SRC_FILES) $(GEN_HEADER_FILES):
    python ../../my_script.py

sort 不仅对作为参数传递的单词进行排序,而且还删除重复项)。请注意,我在编译的先决条件列表中添加了 headers,并且我在相应的配方中将 $^ 自动变量更改为 $<(第一个先决条件)。另请注意,我在所有可能的地方都使用了自动变量。最后,我添加了一个 .PRECIOUS 规则来保护生成的文件不被自动删除。适应您的需求。

重要说明:此解决方案有一个缺点。如果您 运行 并行制作(例如 make -j4),您可能会看到 python 脚本多次 运行。这是因为 make 不知道一个 运行 会创建所有丢失的源文件。这很烦人,不仅因为性能,还因为它可能会在 python 脚本和编译器之间产生竞争条件。不幸的是,仍然没有直接的方法告诉 make 一个配方会产生多个文件。但是有一个可以在那里使用的模式规则的特殊性:对于具有多个目标的模式规则,make 认为所有目标都是由一次执行配方产生的。来自 GNU make 手册:

Pattern rules may have more than one target. Unlike normal rules, this does not act as many different rules with the same prerequisites and recipe. If a pattern rule has multiple targets, make knows that the rule’s recipe is responsible for making all of the targets. The recipe is executed only once to make all the targets. When searching for a pattern rule to match a target, the target patterns of a rule other than the one that matches the target in need of a rule are incidental: make worries only about giving a recipe and prerequisites to the file presently in question. However, when this file’s recipe is run, the other targets are marked as having been updated themselves.

因此,如果您可以使用如下模式规则表示源文件生成:

foo%.c bar%.c baz%.c foo%.h bar%.h baz%.h:
    ...

(其中 % 通配符 必须至少匹配一个字符 ),您就完成了。在你的情况下,你可以重命名生成的文件 {a,b,d}_gen.c:

GEN_SRC_FILES := a_gen.c b_gen.c d_gen.c)
...
a_g%n.c b_g%n.c d_g%n.c a_g%n.h b_g%n.h d_g%n.h:
    python ../../my_script.py

当然,自动创建目标列表会更好。为此,我们将利用 make patsubst 函数仅替换替换字符串中找到的第一个 % 的事实:

$(patsubst %_gen.c,%_g%n.c,$(GEN_SRC_FILES)) $(patsubst %_gen.h,%_g%n.h,$(GEN_HEADER_FILES)):
    python ../../my_script.py

如果重命名不是一个选项,并且您找不到可以完成这项工作的模式规则,那么还有最后一种可能性:递归 make(make 调用 make):

GEN_SRC_FILES    := a.c b.c d.c
GEN_HEADER_FILES := $(patsubst %.c,%.h,$(GEN_SRC_FILES))
GEN_FILES        := $(GEN_SRC_FILES) $(GEN_HEADER_FILES)
SRC              := $(sort $(wildcard *.c) $(GEN_SRC_FILES))
OBJ              := $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRC))

ifneq ($(sort $(wildcard $(GEN_FILES))),$(sort $(GEN_FILES)))
all:
    python ../../my_script.py
    $(MAKE)
else
all: lib

$(BUILD_DIR)/%.o: %.c %.h
    $(CC) -c $< -o $@ $(CFLAGS) -I$(INCLUDE_PATH)

lib: $(BUILD_DIR)/lib_x.a

$(BUILD_DIR)/lib_x.a: $(OBJ)
    $(AR) $(ARCHIVE_OPTIONS) $@ $^
endif

最后一点:最好告诉 make 生成的源文件取决于它们的先决条件(python 脚本,加上一些其他数据文件,也许):

$(GEN_SRC_FILES) $(GEN_HEADER_FILES): ../../my_script.py ../../my_data_file.txt
    python $<

或:

$(patsubst %_gen.c,%_g%n.c,$(GEN_SRC_FILES)) $(patsubst %_gen.h,%_g%n.h,$(GEN_HEADER_FILES)): ../../my_script.py ../../my_data_file.txt
    python $<

但是将最后的改进与递归 make 解决方案相结合是很棘手的(留作练习...)