最近学习c++的工程编译,学习到makefile时出现了一些自己很不理解的地方,经过一番实验总算得到答案.考虑到网上暂时没有相关的文章,我决定分享一下自己探究的结果.
我们知道,在makefile中是可以设置条件判断的,但是在学习中搜索博客能够遇到的格式大抵是这样:
ifeq($(CC),gcc) #判断体
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo:$(objects) #目标,二者互不嵌套
$(CC) -o foo $(objects) $(libs)
就是很简单的先有if-else判断,再出现目标,默认执行标签之前lf-else会被执行.但是显然实际的工程不是这样.我在阅读linux的makefile源码时显然不是这样.比如这里节选了一段linux源码的makefile:
ifdef CONFIG_MODULES #判断体
all: modules #内嵌目标
ifdef CONFIG_MODVERSIONS #目标内嵌判断
KBUILD_BUILTIN := 1
endif
else
KBUILD_BUILTIN := .... #变量定义
KBUILD_MODULES := 1
....
prepare: #目标
......
help:
.....
endif # KBUILD_EXTMOD
这里省略了内容,很明显,在if里面出现了数个嵌套的判断和目标.那这些标签究竟以什么标志结束?它们有时怎么执行的?
首先阐明结论:
①在目标内嵌套判断和在判断内嵌套目标均是允许的,但是不允许缩进.
②目标内嵌套判断,外圈的目标只要遇到新的目标声明或者变量定义就会立即结束.[即使判断体还没结束,注意变量定义和目标声明必须不缩进,否则会被识别为shell指令]
③判断内内嵌目标,内部的目标以遇到变量定义或者遇到else/endif为结束条件.
④执行一个目标之前,会执行所有的没有被嵌套的判断,即使这个判断在目标的后面.
⑤如果在某个目标引用了一个嵌套在判断的目标,而这个目标因为判断条件不满足而不可达,会报错.
但是干说显然时不好理解的,我们下面就来上案例:
实例探究
第一条很好理解,但是判断关键字(ifxx,else,endif)和目标声明都是不允许缩进的.例如以下是一个反例
ifdef as
targ1:#不允许缩进,应将此行的tab删除
echo asdfg
endif
执行会报错
makefile:2:commands commence before first target. stop.
第二条让我们来进行实验:目标内嵌套判断结束符的分界:外圈的目标只要遇到新的目标声明或者变量定义就会立即结束.
案例1:
targ1:
echo "jjj"
ifdef var1:#var1已定义
echo "ppp"
res:=no
echo "lll" #报错:8: *** recipe commences before first target. Stop.
endif
案例2:
targ1:
@echo "jjj"
ifdef var1: #var1已定义
tf:
@echo "lll"
endif #最终输出:jjj
从案例1当中,我们看出,res:=no赋值语句执行后,目标targ1即告结束,此时因为echo "lll"没有了目标归属,因此会报错缺少目标声明.把第6行删除,第2,4行能够被正常执行.
从案例2当中,我们看出,因为出现了新的标签声明,因此targ1的执行即告结束,目标tf会直接被跳过不执行,直接到达endif.这也表明,在判断体内的目标,除非指明了要执行它,或者要执行的目标依赖了它,否则都会直接跳过不执行.
第三条:判断内内嵌目标,内部的目标以遇到变量定义,新的目标声明或者遇到else/endif为结束条件.有了前面的举例,大家这个应该比较清楚了.下面是例子:
ifdef var1 #var1已定义
targ1:
echo sss
else #targ1结束
echo ppp
endif
此时若var1已经定义,输出sss,而var1未定义,输出错误5: *** recipe commences before first target. Stop.后者表明echo ppp这一行被孤立了,证明此原则的正确性,但是注意到如果判断没走到这孤立的一行却可以正确执行.
第四条:执行一个目标之前,会执行所有的没有被目标嵌套的判断,即使这个判断在目标的后面.
ifdef var1 #var1已定义.判断体1
A:=1
endif
targ1:
echo jjj
ifdef var1 #判断体2
B:=2
endif
targ2:
echo $(A) $(B) $(C)
D:= 4 #此条指令用于作为targ2的结束符
ifdef var1 #判断体3
C:=3
endif
我们执行命令 make targ2 指定执行目标targ2,最终输出1 2 3,表明三个判断体全部被执行.值得注意的是,判断体3不在目标内且在目标后面,但是仍然被执行了.
第五条:如果在某个目标引用了一个嵌套在判断的目标,而这个目标因为判断条件不满足而不可达,会报错.
ifndef var1 #var1已定义,所以这个判断体不会被执行
targ1:
echo sss
endif
targ2:targ1 #报错:No rule to make target 'targ1', needed by 'targ2'. Stop.
echo lll
很显然,targ1是不可达的,而它又被targ2所需要,若指定执行make targ2,就会报错.