Makefile学习

makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译, 甚至进行更复杂的功能操作,因为makefile 像一个Shell 本一样,其中也可以执行操作系统的命令。

makefile规则

makefile的规则,也是makefile最核心的内容:

target ... : prerequisites ...
     command
    ...
    ...

  • target 可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标” 节中会有叙述。
  • prerequisites 生成该target 依赖的文件和/或 target
  • command 该target要 行的命令(任意的shell命令)

prerequisites中如果有一个以上的文件比 target文件要新的话,command 定义的命令就会被执行。

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab 键作为开头

Makefile组成

Makefile 主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

    1. 显式规则。显式规则说明了如何生成一个或者多个目标文件。这是由Makefile的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
    1. 隐晦规则。 由于make有自动推导的功能, 所以隐晦的规则可以让我们比较简单地书写Makefile,这是 make所支持的。
    1. 变量定义。在Makefile中要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏, Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
    1. 文件指示。其包含了三个部分,一个是在一个Makefile中引用另一个Makefile, 像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分, 像C语言中的预编译#if一样;还有一个就是是定义一个多行的命令(后续编辑补充)
    1. 注释 。Makefile中只有行注释,和UNIX的Shell脚步一样,其注释是 # 字 ,如果你要在你的Makefile中使 # 字 ,可以使用反斜框进行转义 : \#。

最后还值得一提的是,在Makefile中的命令, 必须要以Tab键开始 。

Makefile工作方式

GNU的make工作时的执行步骤如下:

  1. 读入所有的Makefile。
  2. 读入被include的其它Makefile。
  3. 初始化文件中的变量。
  4. 推导隐晦规则,并分析所有规则。
  5. 为所有的目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些目标文件需要重新生成。
  7. 执行生成命令。

1-5 为第一个阶段,6-7为第二个阶段 。 第一个阶段中, 如果定义的变量被使用了,那么make会把其展开在使用的位置。但make并不会完全马上展开 ,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

伪目标

正如下面例子中的“clean”一样,既然我们生成了许多编译文件, 也应该提供一个以清除它们为“目标”的target,以备以后完整地再次编译(如以“make clean”来使用该目标)

clean:
    rm *.o temp

因为, 我们并不生成 “clean”这个文件。“伪目标”并不是一个文件,只是一个标签, 由于“伪目标”不是文件, 所以make无法生成它的依赖关系和决定它是否要执行。 我们只有通过显式地指明这个“目标” 能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就去了“伪目标”的意义了。
为了避免和文件重名的这种情况, 我们可以使 一个特殊的标记 “.PHONY” 来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY : clean
clean:
    rm *.o temp

变量

变量在声明时需要给予初值,而在使用时,需要在变量名前加上 $ 号,但是最好用小括号() 或者大括号 {} 将边看给包括起来。 如果你要使用真实的 $ 字符,那么你需要使用 $$ 来表示。

objects = program.o foo.o
program : $(objects)
    cc -o program $(objects)

自动化变量

自动化变量会把模式中所定义的一系列的文件自动地挨个取出,直到所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
下面是所有的自动化变量及其说明:

  • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么$@ 是匹配于目标中模式定义的集合。
  • $% : 仅当目标是函数库文件中,表示规则中的目标 员名。例如,如果一个目标是foo.a(bar.o) ,那么, $% 是 bar.o , $@ 是 foo.a 。如果目标不是函数库文件(Unix下是.a,Windows下是 .lib),那么其值为空。
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $<将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $? : 所有比目标新的依赖目标的集合,以空格分隔。
  • $^ : 有的依赖目标的集合,以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只报了一份。
  • $+ : 这个变量像$^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $* : 这个变量表示目标 中 % 及其之前的部分。 果目标是 dir/a.foo.b ,并且目标的 是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有用。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make 识别的,那么 $* 是除了后缀的那一部分。例如: 如果目标是 foo.c ,因为 .c 是make 能识别的后缀名, 所以 $* 的值 是 foo 。这个特性是GNU make的, 很有可能不兼容于其它版本的make, 所以你应该尽量避免使 $* ,除非是在隐含规则或者是静态模式中。 如果目标中的后缀是make 不能识别的,那么 $*就是空值。

在上述所列出来的自动化变量中。四个变量( $@ 、 $< 、 $% 、 $* )在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或者是在当前目录下符合模式的文件名,只需要加上 D 或者 F 字样。。这是GNU make中老版本的特性,在新版本中使 用函数 dir 或者 notdir 就可以做到了。

make的运行

make命令执行后有三个退出码:
0 表示成功执行。
1 如果make运行时出现任何错误,其返回1。
2 如果你使用了make的“-q”选 ,并且make使 一些目标不需要更新,那么返回2。


make的-debug[=]选项, 输出make的调试信息。它有几种不同的级别可供选 , 如果没有参数,
那 是输出最简单的调试信息。下面是的取值:
• a: 也就是all,输出所有的调试信息。(会非常的 )
• b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
• v: 也就是verbose,在b选项的级别之上。输出的信息包含哪个makefile被解析,不需要被重编译的依赖文件(就是依赖目标)等。
• i: 也就是implicit,输出所有的隐含规则。
• j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
• m: 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

TIPS

  • (1) make支持三个通配符:* ,? 和~ 。这是和Unix的B-Shell是相同的。
    通配符同样可以使用在Makefile变量中,如下所示:

objects = *.o

但这并不是说 *.o 会展开 ,不!objects的值是 *.o 。Makefile中的变 其实 是C/C++中的宏。 如果你要让通配符在变量中展开 ,也就是让objects的值是所有 .o的文件名的集合,那么可以这样:

objects := $(wildcard *.o)

  • (2) GNU的C/C++编译器都支持一个“-M”的选 ,即自动找寻源文件中包含的头文件,并生成一个依赖关系, -M参数会 一些标准库的头文件也会包含进来。例如,如果我们执行下面的命令:

gcc -M sha1.c
sha1.o: sha1.c /usr/include/stdio.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h
/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include/stddef.h
/usr/include/bits/types.h /usr/include/bits/typesizes.h
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h
/usr/lib/gcc/x86_64-redhat-linux/4.4.7/include/stdarg.h
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h
/usr/include/string.h /usr/include/xlocale.h /usr/include/stdint.h
/usr/include/bits/wchar.h sha1.h

如果使用 -MM 参数,则不会包含标准库的头文件,大多数其它c/c++编译器使用-M参数即可:

gcc -MM sha1.c
sha1.o: sha1.c sha1.h

于是由编译器自动生成的依赖关系,这样一来,不必再手动书写若干文件的依赖关系,而 编译器自动生成了。

  • (3) make会将其要执行的命令行在命令执行前输出到屏幕上。 当我们使用@字符在命令行前,那么这个命令便不会被make显示出来。

  • (4)如果make执行时,带入make参数 -n 或者 --just-print ,那么其只会显示命令,但不会执行命令,这个功能很有利于我们调试Makefile,看看书写的命令是执行起来是什么样子的或是什么顺序的。
    而make参数-s 或者 --silent --quiet 则是全面禁止命令的显示。
    在“嵌套执行”中一个比较有用的参数, -w 或者 --print-directory 会在make的执行过程中输出一些信息,让你看到当前的工作目录。

  • (5)如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。 你的第一条命令是cd命令,你希望第二条命令是在cd之后的基础上运行,那么你不能将这两条命令写在两行上,而应该将这两条命令写在一行上, 分号分隔。例如:
    示例1:

exec:
    cd /home/temp
    pwd

示例2:

exec:
    cd /home/temp; pwd

当我们执行 make exec时, 第一个例子中的cd没有作用,pwd会打印出 当前的Makefile目录,而第二个例子中,cd 起作用了,pwd会 打印出“/home/temp”。

  • (6) 每当命令运行完后,make会检测每个命令的返回码, 如果命令返回成功,那么make会执行下一条命令, 规则中所有的命令成功返回后,这个规则算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make会终止执行当前规则,这将有可能终止所有规则的执行。
    如果我们希望忽略命令的出错,不影响以后的命令的执行,我们可以在Makefile的命令行前加一个减号 -(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

clean:
    -rm *.o temp

例外给make加上 -i 或者 --ignore-errors 参数,那么Makefile中所有命令都会忽略错误。而如果一个规则是以 .IGNORE 作为目标的,那么这个规则中的所有命令都会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜好设置。
还有一个要提一下的make的参数的是 -k 或者 --keep-going ,这个参数的意思是:如果某个规则中的命令出错了,那么终止该规则的执行,但继续执行其它规则。

  • (7)make的“隐含规则”功能会自动为我们去推导目标的依赖目标和生成命令。make会在自己的“隐含规则”库中寻找可以使用的规则, 如果找到,那么就会使用。如果找不到,那么就会报错。当然我们也可以使 make的参数 -r 或者 --no-builtin-rules 选项来取消 所有的预设置的隐含规则。

参考网址:http://seisman.info/how-to-write-makefile.html

你可能感兴趣的:(Makefile学习)