作 Linux C++,一个稳定的工程,Makefile 是不多改动的。可是若是须要修改的时候,Makefile 的语法和用法一时半会就回忆不出来(原谅我记忆力差……)。在此把本身之前的 Makefile 学习笔记记录一下吧,也做为分享。本文假设读者已经懂得了 Makefile,所以主要是做为备忘和速查用。git
全文中尖括号部分表示变量。本文地址:http://www.javashuo.com/article/p-aaofsoxf-gb.htmlgithub
另外,速查系列还有这一篇:正则表达式速查笔记正则表达式
Makefile 的基本规则就是:shell
target ...: prerequirements ... command ... ...
其中 target
是目标文件,能够有多个,能够是 .o
文件或者是可执行问价,甚至能够是一个标签。Prerequisites
是先决条件,能够是文件,也能够是另外一个 target。
这就组成了一个依赖关系
:target
的先决条件定义在 prerequisites
中,而其生成规则又是由 command 决定的。若是包含多个规则的话,那么第一条规则就是整个 Makefile 的默认规则
。segmentfault
Makefile
或者 makefile
文件.o
文件.o
文件不存在,则寻找 .o
的依赖关系以生成它Makefile 有不少默认的生成规则,可是本文咱们不关心,由于绝大部分状况下,咱们是须要自行写规则的。这便于自定义、便于移植、便于交叉编译、便于调试。安全
变量的定义和调用格式:ide
name = value # 注意变量的值是容许空格的 $(name)
变量值的部分可使用换行符 "\
" 来作假换行,将两行内容链接成一行,从而缩短 Makefile 文件的宽度。函数
文件指示:在一个 Makefile 里面能够制定另外一个 makefile,相似于 C 的 include学习
Makefile 还能够作条件包含动做,相似于 #if
。Makefile 能够定一个变量为一个多行的命令。ui
Makefile 里面只有行注释而没有段注释。注释采用 #
开头。若是要使用 #
字符,则须要转义,写成 “\#
”。
Makefile 规则内容里全部的 shell 命令都要以制表符 Tab
开头,注意,空格符是不行的。
默认的 make 文件名为:GNUmakefile
, makefile
, Makefile
,当敲入 make
命令时,会自动搜寻这几个文件。约定俗成使用最后一个。
语法:
include filename ... # 不容许 include 失败 -include filename ... # 容许 include 失败
能够包含路径或者通配符,一行能够包含多个文件。
若是未指定绝对路径或者相对路径,那么 make 会按照一下的顺序去寻找:
<prefix>/include
(通常是 /usr/local/bin
或 /usr/include
)建议仍是手动指定吧,自动搜寻意外可能太多了。
这里主要是要提醒:不要设置这个环境变量,不然会影响全局的 make include 动做。
Make 支持三个通配符:*, ?, [...]
。能够用在规则中,也能够用在变量中。
伪目标就是 Makefile 里面颇为常见的 .PHONY
标识,好比:".PHONY: clean
",表示这个规则名并不表明一个真实存在的、须要生成的文件名,而只是一条纯粹的规则。
除了 make clean
以外,伪目标还有另外一种使用场景,就是一个 make 动做,实际上生成了多个目标。好比:
.PHONY: all all: exe install # 包含了生成目标文件,以及安装动做
规则的冒号前面能够有多个 target,表示多个 target 共用这条规则。
若是咱们使用中规中矩的 makefile 写法,那么对于每一个源文件都要好好写头文件依赖关系,从而在头文件更新的时候,能够自动从新编译依赖于这个头文件的源文件。
这实在是太麻烦了。好在 gcc
里有一个 -MM
(注意不是 “-M”) 的选项,能够分析出 .c
文件依赖的头文件而且打印出来。所以制做 Makefile 的时候,就能够利用这一特性自动生成依赖。
实现方法有不少,这里贴出我本身使用的例子,也能够参见个人工程代码:
EXCLUDE_C_SRCS =# C_SRCS = $(filter-out $(EXCLUDE_C_SRCS), $(wildcard *.c)) C_OBJS = $(C_SRCS:.c=.o) $(C_OBJS): $(C_OBJS:.o=.c) $(CC) -c $(CFLAGS) $*.c -o $*.o @$(CC) -MM $(CFLAGS) $*.c > $*.d @mv -f $*.d $*.d.tmp @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d @rm -f $*.d.tmp
这里的命令,指的是在 Makefile 规则里的 “command” 部分。
将 “@
” 放在一条命令的前面,表示实际执行的时候,不打印这条命令语句,能够节省屏幕内容,减小垃圾信息(特别是个人自动生成依赖的命令,调通了以后,那就是一堆无用信息)。若是将 “-
” 放在命令前面,则表示无视这条命令的返回值是否为成功(0).
若是上一条命令的结果须要用于下一条命令时,须要将这些命令写在一行中。建议用 “\
” 分开。这最典型的是 cd
命令及其以后的一连串命令。
Make 的时候加上 -n
选项或 --just-print
选项,则表示不执行 make,而只是把过程打印出来。
在 Makefile 里能够到另外一个目录下执行 make,执行方式相似于普通的命令调用,但特别的是,make 能够识别出这是一条嵌套 make 指令,从而在 shell 中打印出 “专项哪里哪里 make” 的提示语法为:
subsystem: $(MAKE) -C subdir
这个作法的主要好处是能够向下级 Makefile 传递变量或者语法:
export VARIABLE ... # 将相应变量变成当前 make 操做的全局变量
也恶意直接指定变量的值:
export VARIABLE = value
若是要传递全部变量(不推荐),直接写 export
就好。
注意由两个系统变量 SHELL
和 MAKEFLAGS
是永远传递的。
此外还有一个全局变量 MAKELEVEL
用来表示当前的嵌套层数。
命令包相似于宏、子函数等等。使用 define
来定义,以 endif
结束,好比:
define run-yacc yacc $(firstword $^) mv y.tab.c $@ endif
注意若是是命令的话,须要以制表符
开头。调用这个命令包的方式为:$(run-yacc)
变量定义时必须赋值,至少赋一个空值(只有等号,等号右边什么都没有)
使用变量的时候虽然不强制、可是为了安全起见,应该使用括号或者打括号把变量包含起来。若是要使用字符 "$
",则使用 "$$
" 来转义
赋值时,等号右侧能够有未定义的变量,而且在其实际使用时才展开变量的内容。但这可能会致使循环引用。为了不这一点,可使用 ":=
" 符号来避免使用未定义的变量
"+=
" 的做用是 “追加” 值。若是右侧有变量未定义,则等价于 “:=
”
"?=
" 的做用是:若是等号左侧的变量未定义,则使用等号右边内容定义,即:
ifeq ($(some_var), undefined) some_var = some_val endif
另外:$@
表示当前规则的编译目标$^
表示当前规则的全部依赖文件$$<
表示当前规则的第一个依赖。
NULL_STR :=# SPACE_STR := $(NULL_STR) # end of line
注意第二行的注释与 “)” 之间是包含一个空格的。注释的 “#” 必须有,不然不会定义一个空格出来。
第一个方式为:$(var: .o = .c)
,意思是将等号左边的字符换成右边的字符
第二个方式为所谓的 “静态模式”:$(var: %.o = %.c)
这很相似于指针,只是地址值变成了变量值。能够用变量值生成变量名,好比:a := $($(var))
或者是 $($(var)_$(idx))
之类的写法。
在命令行调用 make 时,能够直接指定某个变量的全局值,使得它在整个 make 的过程当中一直不变。为了防止这个特性,可使用这个关键字来处理:override <variable> = <value>
等号也能够用 :=
和 ?=
若是某条约束里面不想使用已经定义了的全局变量,能够这样写:
prog: CFLAGS = -g prog: a.o b.o $(CC) $(CFLAGS) a.o b.o
<条件语句> <true 执行语句> else <false 执行语句> endif
其中条件语句有四种情形:
一、表示是否相等
ifeq (<arg1>, <arg2>) # 推荐 ifeq '<arg1>' '<arg2>' ifeq "<arg1>" "<arg2>"
二、表示是否不等,上面的 ifeq 换成 ifneq
三、ifdef
四、ifndef
Make 的全部函数都是内置函数,不能本身定义(命令包除外)。下面列出经常使用的函数,若是看不懂再详细查阅。
$(subst <from>, <to>, <text>)
$(patsubst <pattern>, <replacement>, <text>)
$(strip <string>)
$(findstring <find>, <in>)
$(filter-out <pattern_or_string>, <text>)
$(sort <list>)
$(word <n>, <text>)
$(wordlist <n_start>, <n_end>, <text>)
$(words <text>)
$(dir <names ...>)
$(notdir <names ...>)
$(suffix <names ...>)
$(addsuffix <suffix>, <names ...>)
加先后缀在动态建立局部变量颇有用$(addprefix <prefix>, <names ...>)
$(join <list1>, <list2>)
$(foreach <var>, <list>, <text>)
这实际上是一个函数,做用是:将 list
的单词逐一取出,放到 var
指定的变量中,而后执行 text
的表达式。返回值则是 text
的最终执行值。
执行 shell 命令,而且将 stdout 做为返回值返回,如:contents := $(shell ls -la)
$(error <text ...>)
$(warning <text ...>)
这也同时是调试和定位 make 的好方法。
ifeq ($(FILE), $(wildcard $(FILE))) ... endif