权威资料:http://www.gnu.org/software/make/manual/make.html,到这个页面的尾部有index,可以快速查找一些关键词来定位你想要找的东西,例如automatic variable
一个不错的中文总结:http://www.cnblogs.com/liangxiaxu/archive/2012/07/31/2617384.html
自我学到的一些知识点:
1。makefile中的每一个target都是一个文件,make的最终目标都是生成这个文件,target可以带路径,例如dir1/dir2/test;
2。%.o: %.c中的%可以匹配路径,例如test1/test2/*.o都可以从test1/test2/*.c来推导;
3。关于PHONY目标,能成为PHONY的target应该是不能对应具体文件的,例如clean,例如我们经常用all来收集一批需要产生的真正的target,伪目标的特性就是:当make ABC时,不管当前目录是否有ABC文件,都会去分析其依赖项并执行recipes;
4。如果一个target没有任何依赖,那么这个target永远都是最新的,因此只要这个target文件存在,那么永远不会去执行它的recipes,如果我们把这个target声明为伪目标,那么不论这个target文件是否存在,执行其recipes;
5。一个target依赖多项时,可以分开写,例如:
A: B1
A:
A:;
A: B2
echo $@, $^
最终A依赖于B1和B2
6。可以一次性写多个target的依赖,例如:A B C: D
7。A: PARAM := test.cpp这种写法表明PARAM这个变量的作用域仅仅是生成A时有用
8。A: B C | D E,这里D E被称为order-only依赖,就是说对于D E,和B C一样,该更新就更新,而且还是先处理依赖项D,再处理依赖项E,只不过到最后即便D或者E更新了,也不会因此而更新A,除非B或C更新了,千万不要误以为只要D存在就关心D的死活了(即是否要更新);
9。make test -n会打印出make过程所有需要执行的命令,但并不会执行;
10。make test -d会打印出make过程所有的分析过程,例如怎么解决的依赖,自动推导规则,为什么有些target需要更新,有些不需要;
11。make test -p会打印出make过程中所有的变量和规则的推导结果,即所有变量都展开了,而且会告诉你所执行的命令都来自于哪个makefile的哪一行;
12。make --debug[=FLAGS]
Print debugging information in addition to normal processing. If the FLAGS are omitted, then the behavior is the same as if -d was specified. FLAGS may be a for all debugging output (same as using -d),
b for basic debugging, v for more verbose basic debugging, i for showing implicit rules, j for details on invocation of commands, and m for debugging while remaking makefiles.
13。make V=1的含义是:定义了一个变量,名字是V,值是1,可以在Makefile中通过$(V)来得到V的值;
14。make -j 8的含义是:用8个进程同时build,即速度加快8倍;
15。$+和$^差不多,区别是$^去重了,$+都保留下来了;
16。想查看一个target的所有依赖项,有时候一个target的依赖项会分散在多个文件中,例如对于target all,可以增加如下代码即可:
all:
#$^
这里当然也可以用@echo $^,但上面这种简单写法也可以,反正recipe就是写shell命令
recipe可以由换行,也可以有make的一些条件控制语句,例如:
all:
@echo "hello"
@echo "world"
ifeq "$(abc)" "hello"
@echo "equal"
else
@echo "not equal"
endif
总结:尤其是make -d/-p,基本可以搞定所有你弄不清楚的make问题,仔细分析就好了。
$(if ifeq "foo" "bar", @echo match is broken, @echo match works)
$(if ifneq "foo" "bar", @echo match works, @echo match is broken)
$(if ...) conditional function evaluates to true when the first argument passed to it is non-empty. In you case the condition is literal text: ifeq "foo" "bar", which is, obviously, non-empty.
ifeq/ifneq conditionals are in fact directives, not functions. They can't be used inside variable definition and in functions.
Back to your example, to test string for equality inside the condition use functions like filter, filter-out and findstring:
$(if $(filter-out foo,bar),@echo not equal,@echo equal)
====================================================================
下面是之前学习make时写的一些东西,现在看来好像很垃圾:
[make的规则]
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。
make会比较targets文件和prerequisites文件的修改日期(mtime,而不是ctime或atime),如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
也可以像下面这样把命令放在后面,通过一个分号来搞定
targets : prerequisites ; command
command
比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。也就是说:一行内的命令是在一个子shell环境中执行的。
make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。
当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译XXX模块......
当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:
echo 正在编译XXX模块......
正在编译XXX模块......
如果不给make命令指定target,那么make会将makefile文件中出现的第一个target作为此次执行make的目标,如果该target不是一个,那么取第一个.其实make一次只对一个target负责,除非你在执行make时指定多个target,如make target1 target2
[变量定义]
=定义的时候不会被展开,在具体引用时才会被展开
:=定义的时候就会被展开,建议使用该种变量定义
[自动推导]
main.o : defs.h
make会自动为你添加main.cpp这个依赖文件
[引用其他Makefile]
include foo.make *.mk $(bar)
[make的工作方式]
GNU的make工作时的执行步骤入下:(想来其它的make也是类似)
1. 读入所有的Makefile。
2. 读入被include的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐晦规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
6. 根据依赖关系,决定哪些目标要重新生成。
7. 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
[make命令的参数]
make -i 忽略出错命令,等同于在makefile中对应命令前加上‘-’符号
make -k 如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则
make -n 那么其只是显示命令,但不会执行命令
make -s 或“--slient”则是全面禁止命令的显示
make -w 在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。
[文件搜寻]
Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个 VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
1. vpath < pattern> < directories>
为符合模式< pattern>的文件指定搜索目录< directories>。
2. vpath < pattern>
清除符合模式< pattern>的文件的搜索目录。
3. vpath
清除所有已被设置好了的文件搜索目录。
vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。< pattern>指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
vpath %.h ../headers
该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
[伪目标]
“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。
没有依赖项的target自动成为伪目标,放在.PHONY的依赖项中的target自动成为伪目标.
澄清一个错误观点: 放在.PHONY中的依赖项并不是make后就自动执行,而仅仅只是声明该target是个伪目标而已,make命令依然还是执行makefile中的第一个target
伪目标的更新时间永远都是最新的,因此只要执行到了伪目标,那么必然会被执行,时间戳在它面前无效.
因此对于通过make命令就期望强制执行很多target的话,可以在makefile开头这么写:
all : prog1 prog2 prog3
.PHONY : all
[静态规则]
语法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
看一个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
使用filter:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
[makefile中的函数]
形式为:$(函数名 参数)
自定义函数:
define 函数名
函数体
endef
[gcc技巧]
如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。
[嵌套执行 & 变量或参数向下传递]
嵌套执行make
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
subsystem:
cd subdir && $(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。
我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。
如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:
export <variable ...>
如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:
unexport <variable ...>
如:
export variable := value #定义的同时也export了
export variable += value
如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。
需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile 中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。
但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。
还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:
make: Entering directory `/home/hchen/gnu/make'.
而在完成下层make后离开目录时,我们会看到:
make: Leaving directory `/home/hchen/gnu/make'
当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。
[自动生成依赖关系]
自动生成依赖性
在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:
main.o : main.c defs.h
但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h
于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。
gcc -M main.c的输出是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc -MM main.c的输出则是:
main.o: main.c defs.h
那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让 Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。
于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。
这里,我们给出了一个模式规则来产生[.d]文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式 “%.d”文件,如果有一个C文件是name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是 “name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:
main.o : main.c defs.h
转成:
main.o main.d : main.c defs.h
于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:
sources = foo.c bar.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标