Makefile技巧:让规则依赖y.tab.h但排除在$^变量之外
我正在用Lex和Yacc开发编译器,编译main时需要先通过Yacc生成y.tab.h文件,但不希望这个.h文件被包含在$^变量里——因为编译器不接受把.h文件作为输入。
想问两个问题:
- 能不能让规则依赖
y.tab.h,但把它从$^中排除? - 如果这属于XY问题(即思路本身有问题),有没有更合理的Makefile结构实现目标?
附上当前的Makefile:
targets := ccpy ################################### Options #################################### cc := gcc cFlags := -c -O3 cFlagsLenient := $(cFlags) -w cFlagsHard := $(cFlags) -Wall -pedantic cFlagsDebug := -c -W -Wall -pedantic -g -Og ldFlags := -lm -ly -ll ldFlagsDebug := -g $(ldFlags) ################################### Folders #################################### binDir := ./bin debugDir := ./debug objDir := ./obj srcDir := ./src testDir := ./test ################################ File categories ############################### mains := $(targets:%=$(objDir)/%.o) srcL := $(wildcard $(srcDir)/*.l) srcY := $(wildcard $(srcDir)/*.y) outL := $(srcL:$(srcDir)/%.l=$(srcDir)/%.c) outY := $(srcY:$(srcDir)/%.y=$(srcDir)/%.c) objL := $(outL:$(srcDir)/%.c=$(objDir)/%.o) objY := $(outY:$(srcDir)/%.c=$(objDir)/%.o) cleanY := $(outY) $(srcY:$(srcDir)/%.y=$(srcDir)/%.h) $(srcDir)/y.output $(srcdir)/y.gv srcC := $(filter-out $(outL) $(outY), $(wildcard $(srcDir)/*.c)) objC := $(srcC:$(srcDir)/%.c=$(objDir)/%.o) obj := $(objC) $(objL) $(objY) objDebug := $(obj:$(objDir)/%.o=$(debugDir)/%.do) srcTest := $(wildcard $(testDir)/*.c) tests := $(srcTest:$(testDir)/%.c=$(binDir)/%) #################################### Recipes ################################### # Basic all : $(targets) again : clean all debug: $(targets:%=%.db) test : $(tests) objects: $(obj) # Check Variables info: $(info mains : $(mains)) $(info srcC : $(srcC)) $(info objC : $(objC)) $(info srcL : $(srcL)) $(info outL : $(outL)) $(info objL : $(objL)) $(info srcY : $(srcY)) $(info outY : $(outY)) $(info objY : $(objY)) $(info obj : $(obj)) $(info objDebug : $(objDebug)) $(info srcTest : $(srcTest)) $(info tests : $(tests)) # Final Executables $(targets): % : $(objDir)/%.o $(obj) $(cc) -o $@ $^ $(ldFlags) # Objects directly compiled from C $(objC): $(objDir)/%.o : $(srcDir)/%.c $(cc) $(cFlagsHard) $^ -o $@ # Objects made by Lex and Yacc $(objL) $(objY): $(objDir)/%.o : $(srcDir)/%.c $(cc) $(cFlagsLenient) $^ -o $@ # Files made by Lex $(outL): $(srcDir)/%.c : $(srcDir)/%.l $(outY) lex -o $@ $< # Files made by Yacc $(outY): $(srcDir)/%.c : $(srcDir)/%.y yacc -v -d -Wcounterexamples --file-prefix="$(srcDir)/y" $^ # Debug executables (outdated) $(targets:%=%.db): $(objDebug) $(cc) -o $@ $^ $(ldFlagsDebug) # Debug objects (outdated) $(objDebug): $(debugDir)/%.do : $(srcDir)/%.c $(cc) $(cFlagsDebug) $^ -o $@ # Automated testing $(tests): $(binDir)/%_test : $(testDir)/%_test.c $(filter-out $(mains), $(obj)) $(cc) -o $@ $^ $(ldFlags) $@ clean: rm -rf $(objDir)/%.o $(debugDir)/%.do $(targets) $(tests) $(cleanY) $(outL)
一、让规则依赖y.tab.h但排除在$^之外的方法
完全可以实现,有两种实用方式:
使用过滤函数剔除头文件
如果必须把y.tab.h写在依赖列表里,可以用filter-out函数从$^中移除它:$(objDir)/ccpy.o: $(srcDir)/ccpy.c $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $(filter-out $(srcDir)/y.tab.h, $^) -o $@不过这种方式略显冗余,因为编译C文件时,编译器会通过
#include自动引入头文件,根本不需要把.h文件作为命令行参数。使用仅顺序依赖(Order-only Prerequisites)
Make支持用|分隔符定义仅顺序依赖——这种依赖只会保证目标构建前先完成依赖的构建,但不会被包含在$^、$+等自动变量中:$(objDir)/ccpy.o: $(srcDir)/ccpy.c | $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $^ -o $@这种写法完全符合你的需求:
y.tab.h会被优先生成,但不会出现在$^里,编译命令只会拿到C源文件。
二、更优的Makefile结构(解决XY问题)
你的核心需求本质是:确保所有依赖y.tab.h的代码在编译前,y.tab.h已经生成。正确的做法是让需要头文件的目标文件直接依赖它,而非把y.tab.h放到最终链接的依赖链中。以下是关键优化点:
1. 明确Yacc的双产物规则
Yacc的-d参数会同时生成.c和.h文件,所以需要把两者都作为规则的目标,让Make清楚它们的关联:
$(outY) $(srcDir)/y.tab.h: $(srcDir)/%.y yacc -v -d -Wcounterexamples --file-prefix="$(srcDir)/y" $^
2. 给需要y.tab.h的目标文件加依赖
比如主程序的目标文件ccpy.o如果包含了y.tab.h,就直接添加依赖:
$(objDir)/ccpy.o: $(srcDir)/ccpy.c $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $< -o $@
这里用$<代替$^,只取第一个依赖(即C源文件),彻底避免把头文件传到编译器命令里,这也是编译单个C文件的规范写法。
如果多个C文件都依赖y.tab.h,可以批量处理:
$(objC): $(objDir)/%.o : $(srcDir)/%.c $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $< -o $@
3. 修正Lex的依赖
Lex生成的代码通常需要y.tab.h来引用Yacc定义的token,所以Lex的生成规则应该依赖y.tab.h而非Yacc生成的.c文件:
$(outL): $(srcDir)/%.c : $(srcDir)/%.l $(srcDir)/y.tab.h lex -o $@ $<
优化后的关键规则片段
# Yacc同时生成.c和.h $(outY) $(srcDir)/y.tab.h: $(srcDir)/%.y yacc -v -d -Wcounterexamples --file-prefix="$(srcDir)/y" $^ # Lex生成依赖y.tab.h $(outL): $(srcDir)/%.c : $(srcDir)/%.l $(srcDir)/y.tab.h lex -o $@ $< # 主程序目标文件依赖y.tab.h $(objDir)/ccpy.o: $(srcDir)/ccpy.c $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $< -o $@ # 其他C文件依赖y.tab.h $(objC): $(objDir)/%.o : $(srcDir)/%.c $(srcDir)/y.tab.h $(cc) $(cFlagsHard) $< -o $@ # 最终链接规则不变,仅依赖目标文件 $(targets): % : $(objDir)/%.o $(obj) $(cc) -o $@ $^ $(ldFlags)
这样调整后,既保证了所有依赖y.tab.h的代码都能在头文件生成后编译,又不会把.h文件传到编译器/链接器的命令行,结构更清晰,也符合Makefile的最佳实践。
内容的提问来源于stack exchange,提问作者Ter Maxima

