make
make
是一个构建自动化工具,通常用于管理和加速程序的编译过程。它根据文件的依赖关系来决定哪些部分需要重新编译,并执行相应的编译命令。make
使用一个称为 Makefile
的文件来定义这些依赖关系和规则。
make的运行
在 make
工具中,参数(或选项)用于控制 make
的行为和操作方式。了解和使用这些参数可以帮助你更好地管理和构建项目。以下是 make
工具中常见的参数及其详细介绍:
常用参数
-f file
或 --file=file
或 --makefile=file
指定使用的 Makefile
文件。如果不使用该参数,make
默认查找 Makefile
或 makefile
。
-C directory
或 --directory=directory
在执行之前进入指定的目录。
-j [jobs]
或 --jobs[=jobs]
指定允许的并行任务数量。如果不指定 jobs
,make
将默认选择一个适当的数量。
-k
或 --keep-going
遇到错误时继续构建其他目标。
-i
或 --ignore-errors
忽略所有命令的错误。
-B
或 --always-make
无条件地重新构建所有目标。
-n
或 --just-print
或 --dry-run
或 --recon
仅打印将要执行的命令,但不实际执行。
-q
或 --question
检查目标是否最新。如果是最新的,则返回 0,否则返回非零值。
-s
或 --silent
或 --quiet
禁止显示命令执行的详细信息。
-r
或 --no-builtin-rules
禁用内置的隐含规则。
-R
或 --no-builtin-variables
禁用内置的变量。
-e
或 --environment-overrides
让环境变量覆盖 Makefile
中的变量定义。
-p
或 --print-data-base
打印 make
数据库,包括变量、规则和隐含规则。
-v
或 --version
显示 make
的版本信息并退出。
-d
启用调试输出,显示 make
的详细执行过程。
--debug[=FLAGS]
启用特定的调试信息。
使用示例
假设我们有一个包含以下文件的项目:
main.c
:
foo.c
:
bar.c
:
foo.h
:
bar.h
:
Makefile
:
运行示例
1. 指定使用的 Makefile 文件
2. 在指定目录中执行
3. 并行构建
4. 遇到错误时继续构建其他目标
5. 忽略所有命令的错误
6. 无条件地重新构建所有目标
7. 仅打印将要执行的命令
8. 禁用内置的隐含规则
9. 环境变量覆盖 Makefile 中的变量定义
10. 启用调试输出
基础
- 目标: 可以是一个文件, 目录或任务名
- 依赖项: 目标的依赖项,多个依赖项之间用空格分隔。
- 命令: shell 命令
如果依赖项有变化,则命令将重新执行
Makefile主要内容
-
显式规则: 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
-
隐式规则。由于我们的make有自动推导的功能,所以隐式规则可以让我们比较简略地书写Makefile,这是由make所支持的。
-
变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
-
指令。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。
-
注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用 # 字符,这个就像C/C++中的 // 一样。如果你要在你的Makefile中使用 # 字符,可以用反斜杠进行转义,如: \# 。
包含其他makefile
规则
命令的执行环境
make会以UNIX的标准Shell,也就是 /bin/sh 来执行命令。如果命令太长,你可以使用反斜杠( \ )作为换行符。make对一行上有多少个字符没有限制。
通配符
~
用户家目录?
$?上一条命令返回的状态码*
匹配所有,*.c
表示所有后缀为c的文件
文件搜寻
一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
VPATH变量
指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当前目录永远是最高优先搜索的地方)
vpath关键字
作用与VPATH变量类似, 但是更为灵活
使用方式
该语句表示,要求make在“../headers”目录下搜索所有以 .h 结尾的文件。(如果某文件在当前目录没有找到的话)
目标
伪目标
多目标
静态模式
目标从$object
中获取, %.o
表明要所有以 .o
结尾的目标,也就是 foo.o bar.o
,也就是变量 $object
集合的模式,而依赖模式 %.c
则取模式 %.o
的 %
,也就是 foo bar
,并为其加下 .c
的后缀,于是,我们的依赖目标就是 foo.c bar.c
。而命令中的 $<
和 $@
则是自动化变量, $<
表示第一个依赖文件, $@
表示目标集(也就是“foo.o bar.o”)
自动生成依赖性
由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成, GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个 name.c 的文件都生成一个 name.d 的Makefile文件, .d 文件中就存放对应 .c 文件的依赖关系。
通过模式规则来产生.d文件
所有的 .d
文件依赖于 .c
文件, rm -f $@
的意思是删除所有的目标,也就是 .d
文件,第二行的意思是,为每个依赖文件 $<
,也就是 .c
文件生成依赖文件, $@
表示模式 %.d
文件,如果有一个C文件是name.c
,那么 %
就是 name
, $$$$
意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换。第四行就是删除临时文件。
命令
显示命令
make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @ 字符在命令行前,那么,这个命令将不被make显示出来
命令执行
make的每一条命令都是由不同的shell进程运行的, 即一条命令运行介绍, 运行该条命令的shell将退出, 下一条命令由新运行的shell执行
命令出错
如果命令出错, make将停止后面命令的执行, 如果需要命令出错不退出, 在执行的命令前面加上-
嵌套make
例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
两种方式是一样的
把主目录中Makefile的变量不会覆盖下一层Makefile的变量, 除非使用了-e
参数
如果想传递到下一级Makefile
SHELL
和MAKEFLAGS
总是要传递到下层 Makefile中
定义命令包
变量
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $
符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的 $
字符,那么你需要用 $$
来表示。
变量定义变量
使用 =
号,在 =
左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。
使用的是 :=
操作符, 变量不能使用后面的变量,只能使用前面已定义好了的变量。
变量高级用法
- 变量值的替换
替换变量中的共有的部分,其格式是 $(var:a=b)
或是 ${var:a=b}
,其意思是,把变量var
中所有以a
字串结尾的a
替换成b
字串。这里的“结尾”意思是“空格”或是“结束符”。
静态模式
- 把变量的值再当成变量
追加变量值
override 指令
如果有变量是通过make的命令行参数设置的,那么Makefile文件中对这个变量的赋值会被忽略。如果你想在Makefile文件中设置这类参数的值,那么,你可以使用“override”指令。
多行变量
还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。所以如果你用define定义的命令变量中没有以 Tab 键开头,那么make 就不会把其认为是命令。
环境变量
make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)
目标变量
也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
示例
模式变量
变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
条件判断
使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
目标 foo 可以根据变量 $(CC) 值来选取不同的函数库来编译程序。
函数
函数调用,很像变量的使用,也是以 $
来标识的,其语法:$(<function> <arguments>)
文本操作函数
$(subst from,to,text)
将 text
中的所有 from
替换为 to
。
$(patsubst pattern,replacement,text)
将 text
中所有匹配 pattern
的部分替换为 replacement
。
$(strip string)
移除 string
中的所有前导和尾随空白符。
$(findstring find,in)
在 in
中查找 find
,如果找到则返回 find
,否则返回空。
$(filter pattern...,text)
返回 text
中匹配 pattern
的部分。
$(filter-out pattern...,text)
返回 text
中不匹配 pattern
的部分。
$(sort list)
对 list
进行排序并去重。
$(word n,text)
返回 text
中的第 n
个单词(从1开始计数)。
$(wordlist s,e,text)
返回 text
中从第 s
个到第 e
个单词。
$(words text)
返回 text
中的单词数。
$(firstword text)
返回 text
中的第一个单词。
$(lastword text)
返回 text
中的最后一个单词。
$(dir names)
返回 names
中每个文件名的目录部分。
$(notdir names)
返回 names
中每个文件名的文件名部分。
$(suffix names)
返回 names
中每个文件名的后缀部分。
$(basename names)
返回 names
中每个文件名去掉后缀的部分。
$(addsuffix suffix,names)
给 names
中的每个词添加后缀 suffix
。
$(addprefix prefix,names)
给 names
中的每个词添加前缀 prefix
。
$(join list1,list2)
将 list1
和 list2
中的元素一一对应地连接起来。
文件操作函数
$(wildcard pattern)
返回匹配 pattern
的文件列表。
$(realpath names)
返回 names
中每个文件的绝对路径。
$(abspath names)
返回 names
中每个文件的绝对路径。
条件函数
$(if condition,then-part[,else-part])
根据 condition
的真假选择执行 then-part
或 else-part
。
$(or condition1, condition2, ...)
返回第一个非空的条件。
$(and condition1, condition2, ...)
返回最后一个非空的条件,如果有一个条件为空,则返回空。
Shell 函数
$(shell command)
执行 command
并返回其输出。
其他函数
$(foreach var,list,text)
对 list
中的每个元素执行 text
,var
依次取值为 list
中的每个元素。
$(call func,args...)
调用 func
函数并传递参数 args
。
$(eval text)
对 text
进行求值并执行。
$(value var)
返回 var
的值而不展开其中的变量。
$(origin var)
返回值:
- undefined: var 从来没有定义过,origin函数返回这个值 undefined
- default: var 是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
- environment: var 是一个环境变量,并且当Makefile被执行时, -e 参数没有被打开。
- file: var 这个变量被定义在Makefile中。
- command line: 如果 var 这个变量是被命令行定义的。
- override: var 是被override指示符重新定义的。
- automatic: var 是一个命令运行中的自动化变量。
隐含规则
隐含规则也就是一种惯例,make会按照这种“惯例”心照不宣地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把 .c 文件编译成 .o 文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的 .o 文件。
隐含规则会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量 CFLAGS 可以控制编译时的编译器参数。
使用隐含规则
如果要使用隐含规则生成你需要的目标,就不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果 make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile:
我们可以注意到,这个Makefile中并没有写下如何生成 foo.o 和 bar.o 这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。
隐含规则一览
make
工具中的隐含规则(Implicit Rules)是预定义的一组规则,用于简化常见的构建任务,如编译源文件和生成目标文件。通过使用隐含规则,开发者可以减少在 Makefile
中显式定义的规则数量,从而使构建过程更加简洁和高效。以下是对 make
隐含规则的详细介绍。
常见的隐含规则
编译 C 文件为目标文件
编译 C++ 文件为目标文件
编译 Fortran 文件为目标文件
链接目标文件生成可执行文件
隐含规则的变量
隐含规则使用一些特殊变量,这些变量在规则执行时自动替换为相应的值:
$@
:表示目标文件名。$<
:表示第一个依赖项的名称(通常是源文件)。$^
:表示所有依赖项的名称(去重)。$?
:表示所有比目标文件新的依赖项的名称。$*
:表示不包括扩展名的目标文件名称。
取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上 D 或 F 字样
$(@D)
表示 $@ 的目录部分(不以斜杠作为结尾),如果 $@ 值是 dir/foo.o ,那么 $(@D) 就是 dir ,而如果 $@ 中没有包含斜杠的话,其值就是 . (当前目录)。$(@F)
表示 $@ 的文件部分,如果 $@ 值是 dir/foo.o ,那么 $(@F) 就是 foo.o , $(@F) 相当于函数 $(notdir $@) 。$(*D), $(*F)
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, $(*D) 返回 dir ,而 $(*F) 返回 foo$(%D), $(%F)
分别表示了函数包文件成员的目录部分和文件部分。这对于形同 archive(member) 形式的目标中的 member 中包含了不同的目录很有用。$(<D), $(<F)
分别表示依赖文件的目录部分和文件部分。$(^D), $(^F)
分别表示所有依赖文件的目录部分和文件部分。(无相同的)$(+D), $(+F)
分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)$(?D), $(?F)
分别表示被更新的依赖文件的目录部分和文件部分。
命令的变量
- AR : 函数库打包程序。默认命令是 ar
- AS : 汇编语言编译程序。默认命令是 as
- CC : C语言编译程序。默认命令是 cc
- CXX : C++语言编译程序。默认命令是 g++
- CO : 从 RCS文件中扩展文件程序。默认命令是 co
- CPP : C程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
- FC : Fortran 和 Ratfor 的编译器和预处理程序。默认命令是 f77
- GET : 从SCCS文件中扩展文件的程序。默认命令是 get
- LEX : Lex方法分析器程序(针对于C或Ratfor)。默认命令是 lex
- PC : Pascal语言编译程序。默认命令是 pc
- YACC : Yacc文法分析器(针对于C程序)。默认命令是 yacc
- YACCR : Yacc文法分析器(针对于Ratfor程序)。默认命令是 yacc –r
- MAKEINFO : 转换Texinfo源文件(.texi)到Info文件程序。默认命令是 makeinfo
- TEX : 从TeX源文件创建TeX DVI文件的程序。默认命令是 tex
- TEXI2DVI : 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是 texi2dvi
- WEAVE : 转换Web到TeX的程序。默认命令是 weave
- CWEAVE : 转换C Web 到 TeX的程序。默认命令是 cweave
- TANGLE : 转换Web到Pascal语言的程序。默认命令是 tangle
- CTANGLE : 转换C Web 到 C。默认命令是 ctangle
- RM : 删除文件命令。默认命令是 rm –f
命令参数的变量
- ARFLAGS : 函数库打包程序AR命令的参数。默认值是 rv
- ASFLAGS : 汇编语言编译器参数。(当明显地调用 .s 或 .S 文件时)
- CFLAGS : C语言编译器参数。
- CXXFLAGS : C++语言编译器参数。
- COFLAGS : RCS命令参数。
- CPPFLAGS : C预处理器参数。( C 和 Fortran 编译器也会用到)。
- FFLAGS : Fortran语言编译器参数。
- GFLAGS : SCCS “get”程序参数。
- LDFLAGS : 链接器参数。(如: ld )
- LFLAGS : Lex文法分析器参数。
- PFLAGS : Pascal语言编译器参数。
- RFLAGS : Ratfor 程序的Fortran 编译器参数。
- YFLAGS : Yacc文法分析器参数。