1. make
1.1 make是什么
make是一个命令,是管理文件的自动编译管理器,这里的自动是指能根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时通过读取makefile的文件的内容来进行预期的编译工作,make将只编译有改动的文件,而不用完全编译。
1.2工作原理
1 | app:main.o hello.o |
make
在当前目录寻找Makefile
或makefile
文件。- 如果找到,它会找文件中的第一个目标文件
target
,如上例它找到app
这个目标文件,把这个文件作为最终的目标文件。如果app
文件不存在,或是app
所依赖的后面的.o
文件的文件修改时间要比app
这个文件新,那么make
命令就会执行后面所定义的命令来生成app
这个文件。 - 如果
app
所依赖的.o
文件也不存在,那么make
命令会在当前文件中寻找目标为.o
文件的依赖关系。如果找到,则再根据那一个规则生成.o
文件,根据.o
文件依赖的.c
文件和.h
文件,执行规则生成.o
文件。 - 然后make再用
.o
文件生成执行文件app
总而远之,make
会依据依赖关系一层一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,如最后被依赖的文件找不到,那么 make 就会直接退出并报错。
但是像clean
这种没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行。不过,可以显式指定make
执行clean
,即make clean
。同时只要修改了与第一个目标文件存在直接或间接依赖关系的文件,都会发生重新编译和重新链接
1.3 make命令
make是一个命令工具,它解释Makefile 中的指令(应该说是规则) 1
make [ -f file ][ options ][ targets ]
-f file
:make
默认在工作目录中寻找名为GNUmakefile、makefile、Makefile
的文件作为makefile
输入文件,但有时可能命名不是这些,则-f
可以指定以上名字以外的文件作为makefile输入文件options
v:
显示make工具的版本信息w:
在处理makefile
之前和之后显示工作路径C dir:
读取makefile
之前改变工作路径至dir目录n:
只打印要执行的命令但不执行s:
执行但不显示执行的命令
targets
:使用make
命令时没有指定目标,则make
工具默认会实现makefile
文件内的第一个目标,如果指定了make
工具要实现的目标则该目标为最终目标(目标可以是一个或多个)
2. Makefile
Makefile
是一个脚本文件,其内部编写一些符和make
工具解析的语法规则来进行执行一些命令。
2.1 Makefile是什么
makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile
就像一个Shell
脚本一样,其中也可以执行操作系统的命令。Makefile
带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。makefile
文件由make
命令工具来执行。
make主要解决两个问题:
- 1) 大量代码的关系维护:大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护,把代码维护命令及编译命令写在
makefile
文件中,然后再用make
工具解析此文件自动执行相应命令,可实现代码的合理编译 - 2) 减少重复编译时间,在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。
Makefile
文件命名规则makefile
和Makefile
都可以,推荐使用Makefile
,即vim Makefile
。学号Makefile
只需要学习他的一条规则,三个变量,两个函数就能上手了。
2.2 一条规则
一条规则即指完成单此任务所包含的要素,Makefile的一条规则三要素如下: 1
2
3
4//一条规则
target... : prerequisites ...
command
......
target
:通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可是一个标签prerequisites
:依赖文件,用来输入从而产生目标的文件,一个目标通常有几个依赖文件(也可以没有)。prerequisites
中如果有一个以上的文件比target
文件要新的话,command
所定义的命令就会被执行。command
:make执行的动作,一个规则可以含几个命令(可以没有)。有多个命令时,每个命令占一行。
make
工具从上往下找寻target
,根据target
后对应的依赖关系,先去查找依赖项的文件,为索所有的目标文件创建依赖关系链。若依赖项不存在,则会报错: 1
2trluper@trluper-virtual-machine:~/Documents/staticLib$ make div.o
make: *** No rule to make target 'div.cpp', needed by 'div.o'. Stop.1
2
3
4
5
6
7
8
9
10
11
12
13//Makefile件
sum.o:sum.cpp
g++ -c sum.cpp -o sum.o
main.o:main.cpp
g++ -c main.cpp -o main.o
main:main.o sum.o
g++ main.o sum.o -o main
//使用make
trluper@trluper-virtual-machine:~/Documents/staticLib$ make sum.o main.o main
make: 'sum.o' is up to date.
g++ -c main.cpp -o main.o
g++ main.o sum.o -o main
2.3 变量
在Makefile
中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile
易于维护,修改内容变得简单变量定义及使用。在Makefile
中由自定义变量和自动变量。
2.3.1 等号
在学习变量前需要熟悉Makefile中的等号,等号有4种=,:=,?=,+=
。
?=
表示,如果左边的变量没有被赋值,那么将等号右边的值赋给左边的变量。如果赋过值,则保持原来的值不变。+=
表示将等号右边的值追加到左边变量中,但是中间会有一个空格。=
与:=
是比较不好区分的两个等号,可以将”=“理解为"址传递"或引用,”:=“理解为"值传递”。
在Makefile
中是不允许将变量自己的值赋给自己的,也不允许出现循环引用。
2.3.2 变量名的规则
makefile
的变量名的起名规则:
makefile
变量名可以以数字开头- 变量是大小写敏感的
- 变量一般都在
makefile
的头部定义 - 变量几乎可在
makefile
的任何地方使用
2.3.3 自定义变量
- 1)定义变量方法:
变量名=变量值
,变量名?=变量值
,变量名+=变量值
,变量名:=变量值
- 使用变量名:
$(变量名)
或${变量名}
- 使用变量名:
1 | objs=add.o sum.o div.o mul.o |
2.3.4 三个重要自动变量
Makefile
的自动变量有以下几个:
其中重要常用的三个是:
$@:
表示规则中的目标$<:
表示规则中依赖性的第一个$^:
表示规则中的所有依赖性, 组成一个列表, 以空格隔开,如果这个列表中有重复的项则消除重复项。
1 | objs=add.o sum.o div.o mul.o |
注意:自动变量只能在规则的命令中中使用
2.4 模式规则
如果想要进一步的偷懒,那就必须提到模式规则,模式规则用来匹配当前目录下的符合模式匹配的所有文件。模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符%
,包含有模式字符%
的目标被用来匹配一个文件名,%
可以匹配任何非空字符串。看到上面你觉得很绕,让我们看实例: 1
2
3
4#%表示可以匹配当前目录下的所有以.cpp为后缀的源文件,
#并且对应生成相应名称的.o文件(注意是使用$<才能生成对应.o文件
%.o:%.cpp
g++ -c $< -o $@makefile
不能成功执行,会出现No targets.Stop
错误 1
2
3
4
5
6//直接make,不成功
trluper@trluper-virtual-machine:~/Documents/staticLib$ make
make: *** No targets. Stop.
//传入目标,成功
trluper@trluper-virtual-machine:~/Documents/staticLib$ make main.o
g++ -c main.cpp -o main.o%
是模式匹配规则,你如果没传入目标它就无法执行make
的工作机制。下面来讲解对%
的解决办法
2.5 函数
makefile
中的函数有很多,在这里给大家介绍两个最常用的:
wildcard
查找指定目录下的指定类型的文件1
src = $(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src
patsubst
匹配替换1
obj = $(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o,然后赋值给obj
注意:patsubst
不是把src里的文件的后缀改为.o,是在makefile.o
文件内部将生成的.o
文件替换掉src
内的.cpp
文件,因此在make
的时候一定是要先生成.o
文件(这里从两者*
和%
就能看出),从两者的特性能够得到以下的解决: 1
2
3all:$(patsubst %.cpp,%.o,$(wildcard *.cpp))
%.o:%.cpp
g++ -c $< -o $@*.cpp
文件,使用patsubst
要求将*.cpp
替换为*.o
,但是目录中还没有*.o
文件,则会依据make工作原理进行到下一个目标,刚好这个目标是生成.o
文件的,则执行。
2.6 简单示例
1 | src=$(wildcard *.cpp) |
1 | #执行make |