目录:

[TOC]

1.1. 程序的编译和链接

在Linux下,源文件被编译成中间代码,即.o文件。有时候,由于源文件太多,编译生成的目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给目标文件打个包,在Windows下叫“库文件”,即.lib文件。在Linux下,是Archive File,也就是.a文件。

1.2. Makefile介绍

只要Makefile写的够好,只需要一个make命令就可以完成编译和链接程序,make命令会自动智能地根据当前文件的修改情况来确定哪些文件需要重编译。从而自己编译所需要的文件和链接目标程序。

1.3. makefile的规则

tagfet... : prerequisites...
command

target是一个目标文件,可以是Object File,也可以是可执行文件,还可以是一个标签(Label)
prerequisites就是要生成target目标文件所需要的文件
command是make需要执行的命令
以上是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。

1.4. Makefile里有什么?

Makefile里主要包含了五个东西:显示规则、隐晦规则、变量定义、文件指示和注释。
1.显示规则。显示规则说明了如何生成一个或多个的目标文件。这是由Makefile的书写着明显指出,要生成的文件,文件的依赖性,生成的命令。
2.隐晦规则。由于make具有自动推导的功能,所以隐晦的规则可以让我们比较粗糙简略地书写Makefile,这是由make所支持的。
3.变量定义。
4.文件指示。一个是在一个Makefile中引用另一个Makefile;另一个是根据某些情况制定Makefile中的有效部分。
5.注释。用#
最后在Makefile中的命令,必须以Tab键开始。

1.5. Makefile的文件名

最好使用“Makefile”这个文件名,也可以使用别的文件名,如果要指定特定的Makefile,可以使用make的“-f”和“--file”参数。

1.6. 引用其它的Makefile

在Makefile中可以使用include关键字可以把别的Makefile包含进来,include的语法:
include <filename>
filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)
-include<filename>表示,无论include过程中出现什么错误,都不要报错继续执行。

1.7. 环境变量MAKEFILES

如果在当前环境中定义了环境变量MAKEFILES,那么make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其他的Makefile,用空格分隔。和include不同的是,从这个环境中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也不会处理。但一般不建议使用这个环境变量。

1.8. make的工作方式

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

1.9. 书写规则

1.9.1. 在规则中使用通配符

make支持三个通配符:“*”,“?”,“[...]”。
波浪号(“~”)字符在文件名中有比较特殊的用途。如果是“~/test”,表示当前用户的test目录。“~txl/test”表示用户txl的宿主目录下的test目录。

1.9.2. 文件搜寻

使用变量“VPATH”可以让make在指定目录下找寻文件
VPATH=src:../headers
上面定义了两个目录,“src”和“../headers”,目录由冒号分隔

使用关键字“vpath”也可以,且更为灵活
使用方法:
1.vpath<pattern><directories>
为符合模式<pattern>的文件指定搜索目录
2.vpath<pattern>
清楚符合模式<pattern>的文件的搜索目录。
3.vpath
清楚所有已被设置好了的文件搜索目录。

vpath使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符。例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录。

1.9.3. 伪目标

为了避免和文件重名的这种情况,可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行clean这个目标,只有“make clean”这样。于是整个过程可以这样写:

.PHONY : clean
clean:
rm * .o temp

1.9.4. 多目标

Makefile的规则中目标可以不止一个,其支持多目标,有可能我们的目标同时依赖于一个文件,并且其生成的命令大体类似。

1.9.5. 静态模式

静态模式可以让我们更容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活性。语法如下:

<targets...>:<target-pattern>:<prereq-patterns...>
<commands>

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合
target-pattern是指明了targets的模式,也就是目标集模式
prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义

1.9.6. 自动生成依赖性

1.10. 书写命令

每条命令必须以Tab键开头

1.10.1. 显示命令

用@字符在命令行前,这个命令将不会被显示出来,如
@echo 正在编译XXX模块......
当make执行时,会输出“正在编译XXX模块......”,但不会输出命令
make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令
make参数“-s”或“--slient则是全面禁止命令的显示”

1.10.2. 命令执行

如果要让上一条命令的结果应用在下一条命令时,应该使用分号分隔这两条命令。

1.10.3. 命令出错

在命令前加一个减号“-”,标记为不管命令出不出错都认为是成功的。
还有一个全局方法,给make加上参数“-i”或是“--ignore-errors”参数,makefile的所有命令都会忽略错误。
make的参数“-k”或是“--keep-going”,意为如果某规则中的命令出错了,就终止该规则的执行,继续执行其它的规则。

1.10.4. 嵌套执行make

在一些大的工程中,在每个目录下面都写一个该目录的makefile,例如

subsystem:
cd subdir && $(MAKE)

1.10.5. 定义命令包

如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以define开始,以endef结束。

1.11. 使用变量

1.11.1. 变量的基础

变量在声明时需要给与初值,在使用时,需要在变量名前加上“$”符号,但最好用()或{}把变量包括起来。如果要使用$字符,需要用“$$”来表示。

1.11.2. 变量中的变量

用变量构造变量的值
第一种,用=号,左侧是变量,右侧是变量的值

?=
FOO ?= bar
其含义是,如果FOO被定义过,那么变量FOO的值就是“bar”;如果FOO先前被定义过,那么这条语句将什么也不做

1.11.3. 变量的高级用法

第一种:
替换变量中的共有的部分,格式为
$(var:a=b),即把变量var中以“a”字串结尾的“a”替换成“b”字串,结尾的意思是空格或是结束符。
第二种:
把变量的值再当成变量

1.11.4. 追加变量值

使用“+=操作符给变量追加值”

1.11.5. override指示符

如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果想在Makefile中设置这类参数的值,可以使用“override”指示符。语法:

override <variable> = <value>
override <variable> := <value>

1.11.6. 多行变量

使用define关键字也可以设置变量值,使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令。
define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束

define two-lines
echo foo
echo $(bar)
endef

1.11.7. 环境变量

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已经定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。
如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使用这个变量了。
当make嵌套调用时,上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用export关键字来声明。

1.11.8. 目标变量

可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和全局变量同名。因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。其语法:

<target...>:<variable-assignment>
<target...>:overide<variable-assignment>

第二个语法是针对于make命令行带入的变量,这个变量会作用到由这个目标所引发的所有规则中去。

1.11.9. 模式变量

模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以.o结尾的目标定义目标变量:
%.o:CFLAGS=-O
同样,模式变量的语法和“目标变量”一样:

<pattern...>:<variable-assignment>
<pattern...>:override<variable-assignment>

1.12. 使用条件判断

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

或者
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

<conditional-directive>表示条件关键字,有四个:
ifeq
ifneq
ifdef:
语法是 ifdef<variable-name>
如果变量<variable-name>的值非空,那么表达式为真,否则为假
ifndef

1.13. 使用函数

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和智能。
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function><arguments>)
参数间以逗号分隔,函数名和参数之间以“空格”分隔。

字符串处理函数:

$(subst<from>,<to>,<texl>)
名称:字符串替换函数————subst
功能:把字串<text>中的<from>字符串替换成<to>
返回:函数返回被替换过后的字符串

$(subst ee,EE,feet on the street),
把feet on the street中的ee替换成EE

$(patsubst<pattern>,<>)

1.14 make的运行

1.14.1 make的退出码

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

1.14.2 指定Makefile

我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“--file参数”(“--makefile”参数也行)。
make -f hchen.mk

1.14.3 指定目标

一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。任何在makefile中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。
'all'
这个伪目标是所有目标的目标,其功能一般是编译所有的目标
'clean'
这个伪目标功能是删除所有被make创建的文件
'install'
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
'print'
这个伪目标的功能是例出改变过的源文件
'tar'
这个伪目标的功能是把源程序程序打包备份。也就是个tar文件
'dist'
这个伪目标的功能是创建一个压缩文件,一般是把tar文件压成z文件或者gz文件
'TAGS'
这个伪目标的功能是更新所有的目标,以备完整地重编译使用
'check'和'test'
这两个伪目标一般用来测试makefile的流程

1.14.4 检查规则

有时不想让makefile中的规则执行起来,只想检查一下我们的命令或是执行序列。可以使用如下参数:
'-n'
'--just-print'
'--dry-run'
'--recon'
以上均是不执行参数

'-t'
'--touch'
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成编译过的状态。

'-q'
'--question'
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印一条错误信息。

"-W<file>"
"--what-if=<file>"
"--assume-new=<file>"
"--new-file=<file>"
这个参数需要指定一个文件,一般是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所产生的规则命令。

1.14.5 make的参数

“-b”,“-m”,这两个参数的作用是忽略和其他版本make的兼容性

“-B”,“--always-make”认为所有的目标都需要更新

“-C <dir>”,“--directory=<dir>”指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。

1.15 隐含规则

隐含规则,是makefile中隐含的,早先约定了的,不需要再写出来的规则。
隐含规则会使用一些我们的系统变量,我们可以改变这些系统变量的值来定制隐含规则运行时的参数。如系统变量CFLAGS可以控制编译时的编译器参数。
可以通过“模式规则”的方式写下自己的隐含规则。
使用隐含规则生成需要的目标,所需要做的就是不要写出这个目标的规则。

1.15.1 隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。可以在makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在环境变量中设置这些值,无论怎么样,只要设置了这些特定的量,那么就会对隐含规则起作用。

1.15.2 定义模式规则

可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有“%”字符。“%”的意思是表示一个或多个任意字符。在依赖目标中同样可以使用“%”,只是依赖目标中的“%的取值”,取决于其目标。

自动化变量:
在模式规则中,目标和依赖文件都是一系列的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次对模式规则的解析时,都会是不同的目标和依赖文件。
自动化变量就是完成这个功能的。所谓自动化变量,就是这种变量会把模式所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。下面是所有的自动化变量及其说明:
$a:
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@”就是匹配与目标中模式定义的集合。
$%:
仅当目标是函数库文件中,表示规则中的目标成员名。
$<:
依赖目标中的第一个目标名字。如果依赖目标是以模式(即“%”)定义的,那么“$<”将是符合模式的一系列的文件集。注意,其实一个一个取出来的。
$?:
所有比目标新的依赖目标的集合。以空格分隔。
$^:
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。
$+:
和“$^”很像,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*:
这个变量表示目标模式中“%”及其之前的部分。

(未完,待续......)