如何编写Makefile使$(OBJS)在generate-src执行后再求值?
我有一个包含生成源文件规则的Makefile(简化示例):
SRCS = $(shell find src -name "*.c") OBJS = $(patsubst src/%.c, build/%.o, $(SRCS)) generate-src: @echo "int main() {return 0;}" > ./src/x.c target: generate-src $(OBJS) $(CC) -o target $(OBJS) build/%.o: src/%.c @mkdir -p $(@D) $(CC) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<
执行make target时出现如下错误:
cc -o target build/x.o clang: error: no such file or directory: 'build/x.o' clang: error: no input files make: *** [target] Error 1
问题原因:执行make target时,$(SRCS)会在Makefile解析阶段就通过shell命令求值,此时src/x.c还未生成,导致$(SRCS)为空、$(OBJS)也为空;随后执行generate-src规则创建了src/x.c,但链接命令里的$(OBJS)此时被替换为build/x.o,可这个目标文件并没有被加入target的依赖列表,所以不会触发编译规则生成它。
请问如何编写该Makefile,让$(OBJS)在generate-src执行完成后再进行求值?
方法1:使用递归Make触发二次解析
让target先完成源文件生成,再递归调用Make重新解析变量,此时新生成的源文件已存在,SRCS能正确获取:
target: generate-src @$(MAKE) --no-print-directory real-target real-target: $(OBJS) $(CC) -o target $(OBJS) SRCS = $(shell find src -name "*.c") OBJS = $(patsubst src/%.c, build/%.o, $(SRCS)) generate-src: @echo "int main() {return 0;}" > ./src/x.c build/%.o: src/%.c @mkdir -p $(@D) $(CC) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<
原理:第一次Make仅执行generate-src生成文件,随后启动新的Make进程重新解析整个Makefile,此时find命令能找到刚生成的src/x.c,进而正确生成OBJS并完成编译链接。
方法2:使用延迟展开变量+强制依赖扫描
将SRCS和OBJS设为延迟展开的普通变量(用=而非:=),并在生成文件后触发Make重新扫描依赖:
SRCS = $(shell find src -name "*.c") OBJS = $(patsubst src/%.c, build/%.o, $(SRCS)) generate-src: @echo "int main() {return 0;}" > ./src/x.c @touch src # 更新源目录时间戳,让Make重新扫描src下的文件 target: generate-src $(MAKE) $(OBJS) $(CC) -o target $(OBJS) build/%.o: src/%.c @mkdir -p $(@D) $(CC) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<
原理:延迟展开变量会在每次使用时重新求值,touch src会让Make认为源目录有变化,触发对src/*.c的重新扫描,确保OBJS能获取到新生成的文件,再单独编译目标文件后链接。
方法3:显式指定生成的源文件(适合已知生成文件名的场景)
如果预先知道要生成的源文件名,直接将其加入依赖链,避免动态查找带来的求值时机问题:
GENERATED_SRC = src/x.c OBJS = build/x.o generate-src: $(GENERATED_SRC) $(GENERATED_SRC): @echo "int main() {return 0;}" > $@ target: $(OBJS) $(CC) -o target $(OBJS) build/%.o: src/%.c @mkdir -p $(@D) $(CC) $(CFLAGS) $(CXXFLAGS) -c -o $@ $<
这种方式最直接,完全规避了动态查找变量的求值时机问题,适合生成文件固定的场景。
内容的提问来源于stack exchange,提问作者pdid

