想学习一个短小精悍的C项目,被导流到了cJSON这个C语言的项目.
代码clone下来有一年多了,每每望洋兴叹,感慨这方面的知识匮乏.也曾经盲人摸象式的点滴积累摸索Makefile中的一些细节,终于终于,历经艰深晦涩的一段懵懂,慢慢抚触到了一些精美的珠玑.
引入
学习这类脚本型的语言,或者说工具,最大的拦路虎是各种奇怪的符号和陌生的语法结构,比如,当我第一次注意到Makefile中有$@
和$<
这类的符号,满心都是懵逼的.即使各种百度,面对各种专业
的说法,我的内心是更加崩溃的,这到底是个什么玩意???
自动变量
上述两个符号,其实叫做自动变量,他们专业
的解释是:$@
代表目标文件,$<
代表第一个依赖文件.对于一个颤颤巍巍想窥探Makefile奥秘的新手来说,这是毁灭性的说法.以cJSON中Makefile中出现这两个符号的代码片段为例:
$(CJSON_STATIC): $(CJSON_OBJ)
$(AR) rcs $@ $<
#cJSON_Utils
$(UTILS_STATIC): $(UTILS_OBJ)
$(AR) rcs $@ $<
Makefile的书写规则,顶格写的xxx:yyy
代表的是依赖关系,xxx
代表目标文件,yyy
代表生成xxx
文件所依赖的文件.首行有tab符号的,是根据上一行的依赖关系,要执行的命令.那么第二行,$(AR) rcs
代表调用Linux系统的ar
命令,以rcs
为命令参数,将$<
代表的文件,归档到$@
代表的文件中去.于是就有了如下图示:
这玩意通俗的讲,就是从上面依赖关系里取实际参数的,理解成占位符?或者SQL中防止注入的通配符?$@
代表目标文件,那么取的就是CJSON_STATIC变量所代表的真实值libJson.a,$<
代表的第一个依赖文件,那就是CJSON_OBJ变量所代表的cJSON.o了.
当然,Makefile中还有其他两种类型的自动变量:$^ $? $% $+ $*
各自有不同的意义.单就$@ $<
两个来说,按照上述理解应该是没错了.
后缀依赖
百度半天才感觉这个称呼靠谱点,就是Makefile中一行.c.o
的解释,原文:
.c.o:
$(CC) -c $(R_CFLAGS) $<
先不管.c.o
是干嘛的,直接看第二行,其实就是调用gcc,传入R_CFLAGS代表的编译参数,编译某个C文件.
细读cJson中的Makefile,发现在执行make static
过程中,CJSON_STATIC
依赖CJSON_OBJ
,CJSON_OBJ
依赖cJSON.c
和cJSON.h
两个文件.
但是死活没有找到在那里候执行的gcc编译指令.
然后只能推测.c.o
下面的指令,是gcc编译语句,于是推断,.c.o
的作用,是将目录下所有c文件,执行其后面的指令,编译成目标文件. 结论对吗?不能说错,只不过不是我原来想象的样子.
为了验证我的推断,我新建了目录,写了两个简单的c文件和一个Makefile文件:
其中,a.c和b.c里各有个一简单的函数,Makefile内容如下所示:
a.o:a.c
b.o:b.c
.c.o:
gcc -c $< -o $@
我天真的以为由于有.c.o
的后缀匹配,且表明了各自两个目标文件和源文件的依赖关系,make程序,会自动将我目录下c源文件编译成目标文件.但是每次它只编译了第一行的依赖关系,一度让我很困惑.果然是新手,竟然忘记了Makefile把各种文件和依赖,组织成了依赖树,需要有牵一发的那个点,才能动全身.下图来自知乎话题"Makefile概念⼊⻔"中配图:
显然,最后生成app.executable这个文件时所触发的依赖关系,才是提纲挈领,牵一发而动全身的那个点.
于是,在我的Makefile最上面,增加一行,变成这样,才触发了后缀匹配的编译,形成了依赖树,最终,虽然表明依赖关系的a.o:a.c
和b.o:b.c
后面虽然都没有指明任何指令,但是由于依赖关系满足.c.o
,于是各自执行了gcc -c $< -o $@
指令:
#make后不加任何参数,会默认触发all
all: a.o b.o
a.o:a.c
b.o:b.c
.c.o:
gcc -c $< -o $@
另外,.c.o
是可以被%o:%c
替代的.
伪指令
.PHONY:
就是这个货,像一个泛着黑光的刺,突兀的钉在Makefile中;我知道它很重要,但是众里寻他千百度,我就是理解不了,它是干什么的.山重水复疑无路,柳暗花明又一村,有些东西的理解,对我这种资质平常的人来说,只能靠持续不断的盲人摸象,慢慢理解.
Makefile的书写规则,顶格写的带冒号的,表明依赖关系,其实冒号前后都是文件,意思是,生成冒号前面的目标文件,需要由冒号后面的依赖文件,其下一行的指令操作,才能生成.
而.PHONY:
这个指令,是声明,出现在其后面的单词,在以后的依赖中不代表一个文件,其目的,是让其所声明的依赖关系,永远不成立.那么被.PHONY:
声明的文件后面的指令,会永远执行.
场景:我需要一个clean指令,每次执行make clean,就将目录下所有目标文件删除:
改写我目录中的Makefile:
执行make clean
,则目录下所有的目标文件被删除了;然后在目录下新建一个clean文件,空文件就可以,然后执行make,重新生成目标文件,然后再次执行make clean
,神奇的一幕出现了,clean不执行了!
如上图所示,由于clean被当成了一个目标文件,make程序发现clean文件已经存在了,且不需要更新,所以clean后面的指令就不执行了,而是报了clean文件已经是最新的错误.
这种情况下,就需要用.PHONY:
声明clean不是一个文件了,使clean的依赖条件永远不成立,clean后面的指令,永远能执行:
执行make clean
:
clean后面的指令,被执行了.当然,为了演示,我这个Makefile文件透着怪怪的丑陋,但是就说明问题而言,这种丑陋,正是可贵的/笑哭