1.Makefile规范
target ... : prerequisites ...
command
...
...
target 这 一 个 或 多 个 的 目 标 文 件 依 赖 于prerequisites 中 的 文 件 , 其 生 成 规 则 定 义 在 command (任意的Shell命令)中 。command 是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头。
一个示例
foo.o: foo.c defs.h # foo模块
cc -c -g foo.c
a. 文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c
和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,
那么依赖关系发生。
b. 如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,
如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)
2.书写命令
每条规则中的命令和操作系统 Shell 的命令行是一致的。 make 会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,从上下,从左到右,依次执行。
如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。
exec:
cd /home/hchen; pwd
当我们执行“make exec”时, pwd 会打印出“/home/hchen”。“#”是注释符,很像 C/C++中的“//”,其后的本行字符都被注释。
3.命令出错
命令行前加一个减号“-”(在Tab 键之后),标记为不管命令出不出错都认为是成功的。如:
clean:
-rm -f *.o
例如 mkdir 命令,我们一定需要建立一个目录,如果目录不存在,那么 mkdir 就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用 mkdir 的意思就是一定要有这样的一个目录,于是我们就不希望 mkdir 出错而终止规则的运行。
4.嵌套执行 make
我们有一个子目录叫 subdir,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:
subsystem:
cd subdir && $(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir (-C是大写的C,小写的c编译出目标文件)
定义$(MAKE)宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行 make 命令。
MAKE=make XXXX
如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明:
export
如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:
unexport
5.定义命令包
如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
把这个命令包放到一个示例中来看看吧。
foo.c : foo.y
$(run-yacc)
make 在执行命令包时,命令包中的每个命令会被依次独立执行。
6.使用变量
a.使用“=”号,在“=”左侧是变量,右侧是变量的值
foo = $(ugh)
ugh = Huh?
变量$(foo)的值是“Huh?”($(foo)的值是$(bar),变量是可以使用后面的变量来定义的。
b.使用“:=”操作符
前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
y := $(x) bar
x := foo
那么, y 的值是“bar”,而不是“foo bar”
c.使用“?=”操作符
FOO ?= bar
如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做,在使用时,需要给在变量名前加上“$”符号
7.变量高级用法
第一种是变量值的替换可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
还是看一个示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c) 或者 bar := $(foo:%.o=%.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。
把变量的值再当成变量
x = y
y = z
a := $($(x))
在这个例子中, $(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
追加变量值,我们可以使用“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,我们的$(objects)值变成: “main.o foo.o bar.o utils.o another.o”,如果变量之前没有定义过,那么,“+=”会自动变成“=”
8.override 指示符
有变量是通常 make 的命令行参数设置的,那么 Makefile 中对这个变量的赋值会被忽略。如果你想在 Makefile 中设置这类参数的值,那么,你可以使用“override”指示符。
其语法是:
override =
override :=
当然,你还可以追加:
override +=
例如:
@echo ARCH #x86
override ARCH = arm
9.环境变量
如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile 中使用这个变量了。
当 make 嵌套调用时,上层 Makefile 中定义的变量会以系统环境变量的方式传递到下层的 Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层 Makefile 传递,则需要使用export关键字来声明。
KBUILD_AFLAGS_MODULE := -DMODULE
export KBUILD_AFLAGS_MODULE
10.目标变量或者局部变量
其语法是:
:
: overide
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
不管全局的$(CFLAGS)的值是什么,在 prog 目标,以及其所引发的所有规
则中(prog.o foo.o bar.o 的规则), $(CFLAGS)的值都是“-g”
11.模式变量
make 的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“目标变量”一样:
:
: override
override 同样是针对于系统环境传入的变量,或是 make 命令行指定的变量.
12.自动变量 含义
$* 不包含扩展名的目标文件名称
这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$”的值就是“dir/a.foo”。 如果目标是“foo.c”,因为“.c”是 make 所能识别的后缀名,所以,“$”的值就是“foo”。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$< 第一个依赖文件的名称,依赖目标中的第一个目标名字。如果依赖目标是以模式
(即“%”)定义的,那么“$<”将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开
$@ 目标文件的完整名称,表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@”就是匹配于目标中模式定义的集合。
$^ 所有不重复的依赖文件,以空格分开
$% 仅当 目 标是函数库 文件中,表 示规则中的 目标成员名 。例如,如 果一个目标 是“foo.a(bar.o)”,那么,“$%”就是“bar.o”,“$@”就是“foo.a”。如果目标不是函数库文件(Unix 下是[.a], Windows 下是[.lib]),那么,其值为空。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,所以依赖目标就是“foo.c bar.c”。
“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)
OBJS = kang.o yul.o
CC = gcc
CFLAGS =-Wall -O -g
david : $ (OBJS)
$(CC) $^ -o $@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
$^:为$ (OBJS) ,既kang.o yul.o
$<: kang.c
% 与 * 区别
“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件.
它是在GUNmake的语法层次上的,例如 vpath %.h ../headers ,该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件.
是Shell所支持的通配符,是在shell的语法层次上,.c,一般用在shell命令里面,如:
clean:
rm -f *.o
make 自己的变量
环境变量, 比较重要的是PATH, PWD
cc 是 /usr/bin/cc -> /usr/bin/gcc
CXX 是 g++
INCLUDE_DIRS = /usr/include /usr/local/include /usr/include
MAKE_VERSION #make 版本
CURDIR #make 执行时的所在目录
MAKEFILE_LIST #make 用到的文件
MAKECMDGOALS #make的目标
13.使用条件判断
下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用 GNU 函数编译目标
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
其他条件关键字是ifneq。ifdef。ifndef
14.使用函数
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$( )
或是
${ }
字符串处理函数
subst patsubst strip findstring filter filter-out sort word wordlist
words firstword
文件名操作函数
dir notdir suffix basename addsuffix addprefix join
其他
foreach if call origin shell error
$(subst ,,)
名称:字符串替换函数——subst。
功能:把字串
返回:函数返回被替换过后的字符串。
示例:
$(subst ee,EE,feet on the street)
把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。
$(patsubst ,,)
名称:模式字符串替换函数——patsubst。
功能:查找
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.obar.o”
$(strip )
名称:去空格函数——strip。
功能:去掉
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c )
把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。
$(findstring ,)
名称:查找字符串函数——findstring。
功能:在字串
返回:如果找到,那么返回
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)
$(filter ,)
名称:过滤函数——filter。
功能:以
词。可以有多个模式。
返回:返回符合模式
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。
$(filter-out ,)
名称:反过滤函数——filter-out。
功能:以
返回:返回不符合模式
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
返回值是“foo.o bar.o”。
$(sort )
名称:排序函数——sort。
功能:给字符串中的单词排序(升序)。
返回:返回排序后的字符串。
示例:
$(sort foo bar lose)返回“bar foo lose” 。
备注: sort 函数会去掉中相同的单词。
$(word ,)
名称:取单词函数——word。
功能:取字符串
返回:返回字符串
示例:
$(word 2, foo bar baz)返回值是“bar”。
$(firstword )
名称:首单词函数——firstword。
功能:取字符串
返回:返回字符串
示例: $(firstword foo bar)返回值是“foo”。
备注:这个函数可以用 word 函数来实现: $(word 1,
以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道, make 使用“VPATH”变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 CFLAGS,如:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) #VPATH
指定make命令查找的路径如 果 我 们 的 “$(VPATH) ” 值 是 “src:../headers ”, 那 么 “$(patsubst%,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”,这正是 cc 或gcc 搜索头文件路径的参数。
$(notdir )
名称:取文件函数——notdir。
功能:从文件名序列
指最后一个反斜杠(“/”)之后的部分。
返回:返回文件名序列
示例:
$(notdir src/foo.c hacks)
返回值是“foo.c hacks”。
$(suffix )
名称:取后缀函数——suffix。
功能:从文件名序列
返回:返回文件名序列
示例:
$(suffix src/foo.c src-1.0/bar.c hacks)
返回值是“.c .c”
$(basename )
名称:取前缀函数——basename。
功能:从文件名序列
返回:返回文件名序列
示例:
$(basename src/foo.c src-1.0/bar.c hacks)
返回值是“src/foo src-1.0/bar hacks”。
$(addsuffix ,)
名称:加后缀函数——addsuffix。
功能:把后缀
返回:返回加过后缀的文件名序列。
示例:
$(addsuffix .c,foo bar)
返回值是“foo.c bar.c”
$(addprefix ,)
名称:加前缀函数——addprefix。
功能:把前缀
返回:返回加过前缀的文件名序列。
示例:
$(addprefix src/,foo bar)
返回值是“src/foo src/bar”
$(join ,)
名称:连接函数——join。
功能:把
返回:返回连接过后的字符串。
示例:
$(join aaa bbb , 111 222 333)
返回值是“aaa111 bbb222 333”。
$(foreach ,,)
把参数中的单词逐一取出放到参数所指定的变量中,然后再执行
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中, $(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。
$(if ,)
$(if ,,)
if 函数可以包含“else”部分,或是不含。即 if 函数的参数可以是两个,也可以是三个。
而 if 函数的返回值是,如果
$(call ,,,...)
当 make 执行这个函数时,
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么, foo 的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:
reverse = $(2) $(1)foo = $(call reverse,a,b)。
此时的 foo 的值就是“b a”
$(origin )
注意,
“undefined”
如果
“default”
如果
“environment”
如果
“file”
如果
“command line”
如果
“override”
如果
“automatic”
如果
例如,假设我们有一个 Makefile 其包了一个定义文件 Make.def,在 Make.def 中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境。
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
shell 函数
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统 Shell 的命令。它和反引号“`”是相同的功能。这就是说, shell 函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令 awk, sed 等等命令来生成一个变量,如:
files := $(shell echo *.c)
$(error )
产生一个致命的错误,
$(warning )
这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而make 继续执行。
15.指定 Makefile
我们也可以给 make 命令指定一个特殊名字的 Makefile。要达到这个功能,我们要使用 make 的“-f”或是“--file”参数(“--makefile”参数也行)。例
make -f huang.mk
16.指定目标
一般来说, make 的最终目标是 makefile 中的第一个目标,而其它目标一般是由这个目标连带出来的。这是 make 的默认行为。当然,一般来说,你的 makefile 中的第一个目标是由许多个目标组成,你可以指示 make,让其完成你所指定的目标。要达到这一目的很简单,需在 make 命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)。
任何在 makefile 中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为 make 的终极目标,也就是说,只要 make 可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。
例如下面这个例子:
.PHONY: all
all: prog1 prog2 prog3 prog4
从这个例子中,我们可以看到,这个 makefile 中有四个需要编译的程序——“prog1”,“prog2”, “prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把 all 置成第一个目标,那么只需执行“make”),我们也可以使用“make prog2”来单独编译目标“prog2”。
17.检查规则
-B
--always-make
认为所有的目标都需要更新(重编译)。
-C
--directory=
指定读取 makefile 的目录。如果有多个“-C”参数, make 的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:
make –C ~hchen/test –C prog
-debug[=]
输出 make 的调试信息。
-a
也就是 all,输出所有的调试信息。(会非常的多)
-b
也就是 basic,只输出简单的调试信息。即输出不需要重编译的目标。
-v
也就是 verbose,在 b 选项的级别之上。输出的信息包括哪个 makefile 被解析,不需要被重编译的依赖文件(或是依赖目标)等。
-i
也就是 implicit,输出所以的隐含规则。
-m
也就是 makefile,输出 make 读取 makefile,更新 makefile,执行makefile 的信息。
-d
相当于“--debug=a”。
-e
--environment-overrides
指明环境变量的值覆盖 makefile 中定义的变量的值。
-f=
--file=
--makefile=
指定需要执行的 makefile。
-h
--help
显示帮助信息。
--ignore-errors
在执行时忽略所有的错误。
-I
--include-dir=
指定一个被包含 makefile 的搜索目标。可以使用多个“-I”参数来指定多个目录。
-j []
--jobs[=]
指同时运行命令的个数。如果没有这个参数, make 运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在 MS-DOS中是无用的)
-k
--keep-going
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行。
-n
--just-print
--dry-run
--recon
仅输出执行过程中的命令序列,但并不执行。
-o
--old-file=
--assume-old=
不重新生成的指定的
-q
--question
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是 0 则说明要更新,如果是 2 则说明有错误发生。
-p
--print-data-base
输出makefile 中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行 makefile,你可以使用“make -qp”命令。如果你想查看执行 makefile 前的预设变量和规则,
你可以使用“make –p –f/dev/null”。这个参数输出的信息会包含着你的 makefile 文件的文件名和行号,所以,用这个参数来调试你的 makefile 会是很有用的,特别是当你的环境变量很复杂的时候。
-r
--no-builtin-rules
禁止 make 使用任何隐含规则。
-R
--no-builtin-variabes
禁止 make 使用任何作用于变量上的隐含规则。
-s
--silent
--quiet
在命令运行时不输出命令的输出。
-S
--no-keep-going
--stop
取消“-k”选项的作用。因为有些时候, make 的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。
-t
--touch
相当于 UNIX 的 touch 命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。
-v
--version
输出 make 程序的版本、版权等关于 make 的信息。
-w
--print-directory
输出运行 makefile 之前和之后的信息。这个参数对于跟踪嵌套式调用 make 时很有用。
--no-print-directory
禁止“-w”选项。
-W
--what-if=
--new-file=
--assume-file=
假定目标
--warn-undefined-variables
只要 make 发现有未定义的变量,那么就输出警告信息
18.隐含规则
“隐含规则”也就是一种惯例, make 会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile 中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来, make 会自动推导出这种规则,并生成我们需要的[.o]文件。
我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。
例如,我们有下面的一个 Makefile:
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
因为 make 的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。如果找不到,那么就会报错。
完全没有必要写下下面的两条规则:
foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
编译 C 程序的隐含规则。
“
编译 C++程序的隐含规则。
“
汇编和汇编预处理的隐含规则。
“
链接 Object 文件的隐含规则。
“
“$(CC) $(LDFLAGS)
x : y.o z.o并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o
如果没有一个源文件(如上例中的 x.c)和你的目标名字(如上例中的 x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。
19.关于命令参数的变量
ARFLAGS 函数库打包程序 AR 命令的参数。默认值是“rv”。
ASFLAGS 汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS C 语言编译器参数。
CXXFLAGS C++语言编译器参数。
CPPFLAGS C 预处理器参数。(C 和 Fortran 编译器也会用到)。
LDFLAGS 连接器参数(如“ld”)
20.模式规则介绍
模式规则中,至少在规则的目标定义中要包含“%”,否则,就是一般的规则。目标中的“%”定义表示对文件名的匹配,“%”表示长度任意的非空字符串。例如:“%.c”表示以“.c”结尾的文件名(文件名的长度至少为 3),而“s.%.c”则表示以“s.”开头,“.c”结尾的文件名(文件名的长度至少为 5)。
如果“%”定义在目标中,那么,目标中的“%”的值决定了依赖目标中的“%”的值,也就是说,目标中的模式的“%”决定了依赖目标中“%”的样子。例如有一个模式规则如下:
%.o : %.c ;
其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是“a.o b.o”,那么“%c”就是“a.c b.c”。