导论
- 自动化实现的逻辑:利用函数转化文件名,获取所有.cpp/.c文件对应的.o 文件名。将所有.o文件都列为是目标程序的依赖项,根据makefile的隐含规则,make会编译所有的.cpp/.c文件,生成对应的.o文件,链接.o生成目标程序。
- make编译.cpp/.c文件,依靠的是makefile的隐含规则,可以通过自定义隐含规则实现对这一过程的控制。
- 依靠变量实现Makefile的可配置化
- 使用伪目标,实现多目标构建,工程相关指令封装(clean,install)
- 引用其他Makefile,实现工程嵌套,从而构建大工程
- 扩展源文件搜寻目录,减少路径相关操作
- 本文基于陈皓大神的《跟我一起写Makefiel》
Makefile 入门
三个重要变量
- $@: 目标文件
- $^:所有的依赖文件,
- $<:第一个依赖文件。
重点语法规则
- 反斜杠(\)是换行符的意思
- Shell命令,一定要以一个Tab键作为开头
make自动推导
make 具有隐晦规则,可自动推导,比如make看到一个whatever.o文件,会自动的把whatever.c文件加在依赖关系,cc -c whatever.c 也会被推导出来
Makefile的构成
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则:要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则:make自动推导的。
- 变量的定义:字符串,C语言中的宏,当Makefile被执行时,变量都会被扩展到相应的引用位置上
- 文件指示:
- 引用另一个Makefile
- 根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样
- 定义一个多行的命令。
- 注释:Makefile中只有行注释"#"
Makefile的文件名
- 默认的情况下,make命令会在当前目录下按顺序找寻文件"GNUmakefile"、“makefile”、“Makefile”.
- 可以指定特定的Makefile,使用make的"-f"和"–file"参数,如:make -f Make.Linux或make --file Make.AIX。
引用其他Makefile
include foo.make *.mk $(bar)
- include 支持引入其他make,和变量,且支持通配符
- make 是使用
--include-dir
或者--I
指定,引用目录 - 搜索路径:当前目录 -> 指定目录 -> <prefix>/include(一般是:/usr/local/bin 或/usr/include)
- 兼容其他版本make使用sinclude
make是如何工作的(重点)
- make程序会在当前目录下找名字叫"Makefile"或"makefile"的文件。
- 读入被include 的其它Makefile
- 初始化变量
- 设置目标(一次make,只有一个最终目标),第一个目标为默认最终目标,如果要make非默认最终目标,需要显示执行,如 make clean
- 如果目标文件所依赖的.o文件不存在,就先生成该.o
- 然后层层递归,从而生成一条编译顺序链,非最终目标的的依赖对象不会在编译链中,所以不会被执行
- 最后执行编译命令,层层返回直到生成目标文件
- 没有和目标文件直接或间接关联,不会被自动执行,只是被定义。
不重要的知识点
环境变量MAKEFILES
如果存在环境变量MAKEFILES,make会自动引用,但与include不同的是,MAKEFILES不会主动执行
建议不要用,因为所有的Makefile都会受他影响
清空目标文件的规则
.PHONY : clean
clean :
-rm edit $(objects)
- .PHONY意思表示clean是一个"伪目标"。
- rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事
- 不成文的规矩是——“clean从来都是放在文件的最后”
如何编写规则
通配符
- *: 略
- ?:上一命令的结果
- ~:家目录,windows下是环境变量HMOE
源文件搜寻
-
特殊变量"VPATH"
VPATH = src:../headers
- 给Makefile指定源文件所在目录
- 目录间以冒号:隔开
-
关键字"vpath"
vpath <pattern> <directories> #指定某个目录的某种文件
vpath %.c foo
vpath <pattern> #清除已指定的某种类型的文件
vpath %.c
vpath #清除所有已指定的文件
vpath
- %:表示匹配0或若干个字符(同*)
伪目标
- 顾名思义,具有目标的属性,但不是真正的文件,不会生成目标文件,并且需要指定依赖关系
- 伪目标可不用声明,若伪目标与文件同名,则伪目标必须用
.PHONY
声明,否则编译会报错 - 伪目标可以作为Makefile的默认目标,比如声明一个名为"all"伪目标,依赖多个真实的目标,从而实现一次生成多个目标的目的
多目标(目标变量)
- @ : 目标变量
- 这一节书中说的有绕,我的理解是,有多个目标,不用每个目标都写一套指令,指令要复用,指令涉及到目标名的,用
$@
替代
静态模式
<targets ...> : <target-pattern> : <prereq-patterns ...>
objects = foo.o bar.o
all:$(objects)
$(objects): %.o: %.c #%.c表示.o同名的.c文件
$(CC) -c $(CFLAGS) $< -o $@
目前不大理解其用途,意思就是筛选目标集,并制定依赖关系
自动生成依赖关系
个人感觉没啥用,现在的编译器可以不依赖头文件
利用 gcc/g++的"-M"的选项,获取源文件包含的头文件,然后把所有依赖文件放入一个.d中,编译时把.d文件引入
命令的书写
说明:个人认为,这一部分有挺多知识点都不常用,这些知识点就不记录了
命令出错
- 命令前加一个减号"-"(在Tab 键之后),标记为不管命令出不出错都认为是成功的
- 给 make 加上"-i",所有命令都会忽略错误,"-k",出错则终止
嵌套执行
- $(MAKE) 执行子Makefile
- make的-C选项可以先进入一个文件夹,在执行make
- 嵌套时传递变量:
export variable
;拦截变量传递:unexport variable
- 如果make有参数,除"-C","-f","-h""-o"和"-W" 以外的参数都将传递
- make的-w选项:输出当前目录,-C时,默认-w
定义命令包
-
以"define"开始,以"endef"结束,如
define run-yacc yacc $(firstword $^) mv y.tab.c $@ endef
-
执行:
foo.c : foo.y $(run-yacc)
在命令包的定义中run-yacc," " 就 是 " f o o . y " , " ^"就是"foo.y"," "就是"foo.y","@“就是"foo.c”
命令包的执行需要使用$()
变量的使用
变量的声明
- 变量名可以是数字开头,但不应该含有":"、"#"、"="或是空字符(空格、回车等)。变量是大小写敏感的
- 变量在声明时,需要给予初值
- 变量在使用时,需要给在变量名前加上"$“符号,最好用小括号”()“或是大括号”{}"把变量给包括起来
- MakeFile的变量类似C/C++中的宏,在指定为位置展开,不同的是可以重定义
变量给变量赋值
-
变量可以先使用在定义,但相互嵌套会报错
foo = $(bar) bar = $(ugh) ugh = Huh?
-
:= 表示不使用未声明的变量;?= 表示如果未定义则定义,已定义则跳过
-
如果变量后面有n个空格,变量在展开时,也会附带一个空格
追加变量
- 以使用"+="操作符给变量追加值,如
objects += another.o
- +=
- 变量未定义过,那么,"+=“会自动变成”=",
- 变量已定义,会继承于前次操作,如果前一次的是":=",那么"+=“会以”:="作为其赋值符
系统环境变量
- make在开始运行时,会载入系统环境变量
- 默认系统环境变量是可覆盖的,make的"-e"参数,表示不覆盖
局部变量
局部变量分两种,目标变量和模式变量,区别在于作用不同:目标变量的作用域为目标生成的范围内,模式变量的作用域为符合模式的文件范围
目标变量
格式:<target ...> : <variable-assignment>
在目标后对变量赋值,如下
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
模式变量
格式:<pattern ...>; : <variable-assignment>
如:%.o : CFLAGS = -O
不重要的知识点
-
override 指示符:命令行定义的变量需要override来修改
-
变量值的替换
${var:a=b}
:把变量"var"中所有以"a"字串"结尾"的"a"替换成"b"字串。这里的"结尾"意
思是"空格"或是"结束符"bar := $(foo:%.o=%.c)
:静态模式
-
变量嵌套:把变量值当变量
$($(x))
-
多行变量:使用define&endef定义多行变量,无[Tab] 键开头为变量,有则为命令包
条件判断
-
关键字:ifeq ifneq ifdef ifndef else endif
-
说明make提供四种判断,相等,不相等,定义,未定义;未提供eles if
-
格式:
ifdef foo frobozz = yes else frobozz = no endif
函数
函数调用:$(<fun> <arg>)
字符串处理函数
- 模式替换 patsubst :
$(patsubst <pattern>,<replacement>,<text>)
- 字符串替换 subst:
$(subst <from>,<to>,<text>)
- 去头尾空格 strip:
$(strip <string>)
- 字符串查找 findstring:
$(findstring <find>,<in>)
- 过滤 filter:
$(filter <pattern...>,<text>)
- 反向过滤 filter-out:
$(filter-out <pattern...>,<text>)
- 单词排序 sort :
$(sort <list>)
- 取单词 word:
$(word <n>,<text>)
- 取单词串 wordlist:
$(wordlist <ss>,<e>,<text>)
- 单词个数统计 words:
$(words <text>)
- 首单词 firstword:
$(firstword <text>)
文件名操作函数
- 取目录 dir:
$(dir <names...>)
- 取文件 notdir:
$(notdir <names...>)
- 取后缀 suffix:
$(suffix <names...>)
- 取前缀 basename:
$(basename <names...>)
- 加后缀 addsuffix:
$(addsuffix <suffix>,<names...>)
- 加前缀 addprefix:
$(addprefix <prefix>,<names...>)
- 连接 join——两个list按下标结合,如a[1],b[1]结合:
$(join <list1>,<list2>)
- wildcard——替换 Bash 的通配符:$(wildcard PATTERN…)
其他函数
-
foreach ——遍历list,操作其中的每个元素:
$(foreach <var>,<list>,<text>)
-
if——功能类似ifeq:
$(if <condition>,<then-part>)
或者$(if <condition>,<then-part>,<else-part>)
-
call——调用自定义函数变量
reverse = $(1) $(2) foo = $(call reverse,a,b)
-
origin——获取变量来源:$(origin 😉
-
shell——执行shell命令,获取返回值:$(shell cat foo)
-
error——报错,make暂停(可以继续):$(error <text …>;)
-
warning——报警,但make不会停:$(warning <text …>;)
隐含规则
C/C++隐含规则
-
编译 C 程序的隐含规则:".o"的目标的依赖目标会自动推导为".c",并且其生成命令是
$(CC) –c $(CPPFLAGS) $(CFLAGS)
-
编译 C++ 程序的隐含规则:".o"的目标的依赖目标会自动推导为".cc"或是".C",并且其生成命令是
$(CXX) –c $(CPPFLAGS) $(CFLAGS)
(建议使用".cc"作为 C++ 源文件的后缀,而不是".C")
隐含规则使用的变量
命令的变量
- CC:C 语言编译程序。默认命令是"cc"
- CXX:C++ 语言编译程序。默认命令是"g++"
- AR: 函数库(Object文件)打包程序。默认命令是"ar"
- CPP:C 程序的预处理器(输出是标准输出设备)。默认命令是"$(CC) –E"
- RM: 删除文件命令。默认命令是"rm –f"
命令参数的变量
- ARFLAGS: 函数库打包程序 AR 命令的参数。默认值是"rv"
- ASFLAGS: 汇编语言编译器参数。(当明显地调用".s"或".S"文件时)
- CFLAGS:C 语言编译器参数
- CXXFLAGS:C++ 语言编译器参数
- CPPFLAGS:C 预处理器参数
自定义模式规则
使用"%“来定义一个隐含规则 ,目标的”%“的意思是表示一个或多个任意字符,目标的”%",取决于依赖。变量与函数的"%"在Makefile载入是被展开,模式则在运行时展开
使用自动化变量
一般而言,使用模式是为了实现自动化,所以在模式中需要使用自动化变量实现对具体的文件的"抽象",自动化变量的定义见附录
-
@ 、 @、 @、<、 %、 *在扩展时只会有一个文件
-
? 、 ?、 ?、^、$展开时一个文件列表
-
这些变量使用"D",“F”,可以获取目录或文件名,如
$(<D), $(<F)
模式的匹配
“%“所匹配的内容叫做"茎”,当一个模式匹配包含有斜杠的文件时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。如"e%t”,"src/eat"匹配于该模式,%为a,但目录部分src/也会随之出现在目标中
函数库
函数库文件(.a/.lib)是对 Object 文件(程序编译的中间文件)的打包文件,由命令“ar”来完成打包工作。隐含规则:如"foo.a(bar.o bar1.o)"中bar.o,bar1.o编译成功后,会自动调用ar命令打包,不过个人更喜欢显式的输入ar命令进行打包
附录
GUN制定的伪目标
伪目标 | 定义 |
---|---|
all | 编译所有的目标 |
clean | 删除所有被 make 创建的文件 |
install | 安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去 |
列出改变过的源文件 | |
tar | 源程序打包成tar 文件 |
dist | 创建一个压缩文件,把 tar 文件压成 Z 文件或是 gz文件 |
TAGS | 更新所有的目标,以备完整地重编译使用(没理解) |
check 和 test | 测试 makefile 的流程。 |
make 的参数
参数 | 含义 |
---|---|
-b/-m | 忽略和其它版本 make 的兼容性 |
-B | 重编译 |
-C
| 指定读取 makefile 的目录 |
-debug[=] | 输出 make 的调试信息 |
-e | 环境变量的值覆盖 makefile 中定义的变量的值 |
-f=, | 指定需要执行的 makefile |
-h | 显示帮助信息 |
-i | 在执行时忽略所有的错误 |
-I
| 指定makefile 的搜索目录。可以使用多个“-I”参数来指定多个目录 |
-j [] | 指定同时运行命令的个数 |
-k | 出错也不停止运行 |
-l | 指定 make 运行命令的负载 |
-n | 仅输出执行过程中的命令序列,但并不执行。 |
-o | 不更新生成的指定的 |
-p | 输出所有的规则和变量 |
-q | 是检查所指定的目标是否需要更新,0更新,2有错误发生 |
-r | 禁止 make 使用任何隐含规则 |
-R | 禁止 make 使用任何作用于变量上的隐含规则 |
-s | 在命令运行时不输出命令的输出 |
-S | 取消“-k”选项的作用 |
-t | 把目标的修改日期变成最新的(导致目标不会更新) |
-v | 输出 make 程序的版本 |
-w | 输出运行 makefile 之前和之后的信息 |
-W | 假 定 目 标需要更新 |
自动化变量
变量 | 含义 |
---|---|
$@ | 目标文件集 |
$% | 当目标是函数库文件时有效,表示目标成员名。如是"foo.a(bar.o)",目标为foo.a,$%表示bar.o |
$< | 第一个依赖目标 |
$? | 所有比目标新的依赖目标的集合(需要更新的依赖) |
$^ | 所有依赖目标的集合,去除重复 |
$+ | 所有依赖目标的集合,不去除重复 |
$* | 目标模式中"%“及其之前的部分,。如目标是"dir/a.foo.b”,模式是"a.%.b",那么,"$*“就是"dir/a.foo” |
特殊的目标
名称 | 功能 |
---|---|
.PHONY: | 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。 |
.SUFFIXES: | 这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名 |
.DEFAULT: | Makefile 中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 “.DEFAULT” 所指定的命令。 |
.PRECIOUS: | 这个特殊目标所在的依赖文件在 make 的过程中会被特殊处理:当命令执行的过程中断时,make 不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。 |
.INTERMEDIATE: | 这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。 |
.SECONDARY: | 这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。 |
.IGNORE | 这个目标的依赖文件忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。 |
.DELETE_ON_ERROR: | 如果在 Makefile 中存在特殊的目标 “.DELETE_ON_ERROR” ,make 在执行过程中,荣国规则的命令执行错误,将删除已经被修改的目标文件。 |
.LOW_RESOLUTION_TIME: | 这个目标的依赖文件被 make 认为是低分辨率时间戳文件,给这个目标指定命令是没有意义的。通常的目标都是高分辨率时间戳。 |
.SILENT: | 出现在此目标 “.SILENT” 的依赖文件列表中的文件,make 在创建这些文件时,不打印出此文件所执行的命令。同样,给目标 “SILENT” 指定命令行是没有意义的。 |
.EXPORT_ALL_VARIABLES: | 此目标应该作为一个简单的没有依赖的目标,它的功能是将之后的所有变量传递给子 make 进程。 |
.NOTPARALLEL: | Makefile 中如果出现这个特殊目标,则所有的命令按照串行的方式执行,即使是存在 make 的命令行参数 “-j” 。但在递归调用的子make进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。 |