是看跟我一起写makefile做的一点笔记。
编译和链接
在Makefile中定义整个编译流程以及各个目标文件与源文件之间的依赖关系,并且只重新编译新的修改会影响到的部分,从而降低编译的时间。
编译只检查函数、变量是否被声明,并生成.o
(Linux)或.obj
(Windows)文件。由于编译生成的中间目标文件太多,因此通常会将其打包成为.a
(Linux)或.lib
(Windows)文件,以进行链接。
依赖关系
makefile
target ... : prerequisites ...
command
...
- target:可以是一个目标文件,可执行文件,或一个标签。
- prerequisites:target所依赖的文件和target。
- command:该target要执行的shell命令。
注意makefile中的command必须以
Tab
开头。另外,在执行有顺序性要求的命令时,不应该分写两行,而应该用分号隔开,例如:
makefile
# 错误写法!pwd将在本目录执行
cd ../
pwd
# 正确写法
cd ../; pwd
makefile会检查文件的更新时间,当prerequisites中有文件比target新时,command所定义的命令就会被执行。
以1个.h
文件 + 2个.c
文件为例:
makefile
# myProcess是可执行文件
myProcess : main.o somec.o
gcc -o myProcess main.o somec.o
main.o : main.c somec.h
gcc -c main.c
somec.o : somec.c somec.h
gcc -c somec.c
clean :
rm myProcess main.o somec.o
注意,这里的clean
是一个标签而非文件,使用类似make clean
这样的形式来执行标签后的命令。
makefile会以完成第一个target为目标,逐次展开依赖,去更新涉及到的依赖的文件。
变量
将上例改为用变量的形式:
makefile
OBJ = main.o somec.o
TARGET = myProcess
CC = gcc
$(TARGET) : $(OBJ)
$(CC) -o $(TARGET) $(OBJ)
main.o : main.c somec.h
$(CC) -c main.c
somec.o : somec.c somec.h
$(CC) -c somec.c
clean :
rm $(TARGET) $(OBJ)
也可以将clean改为下面这种写法:
makefile
.PHONY : clean
clean :
-rm $(TARGET) $(OBJ)
其中,.PHONY
显式地表示clean
是一个伪目标 ,即表明clean不是需要检查依赖的目标。
另外,rm
前的-
表示执行时即使出错,也继续执行命令。
一般更推荐使用:=
来赋值变量,与=
的区别在于,前者立即展开,而后者在使用时才会展开。
定义空格变量
使用如下的方法定义空格变量:
makefile
nullstring :=
space := $(nullstring) #end of line
nullstring是空变量,其后加一个空格,在用注释表示结束,即可将space展开为一个空格变量。
变量的替换
例如:
makefile
var1 := a.o b.o
var2 := $(var1:.o=.c)
表示var2是将var1中的.o替换为.c,即var2为a.c b.c。
override
用override
强制修改变量,例如:
makefile
var := var1
override var1 := var2
多行变量
makefile
define multi_line_var
xxx
xxx
endef
目标变量
在目标后的依赖里写变量,表示不管全局上该变量如何,在此目标引发的行为中变量都是这样的,例如:
makefile
CFLAGS := -o
# 由prog引发的行为中CFLAGS都是-g
prog : CFLAGS = -g
自动变量
使用模式规则 来定义一个隐含规则,需要在目标中使用%
。目标中的%取决于依赖中的值。
而自动化变量,就会把模式中所定义的一系列的文件自动地挨个取出。
下面是一些常用的自动变量:
自动变量 | 含义 |
---|---|
$@ | 规则中的目标文件名 |
$< | 第一个依赖文件 |
$^ | 所有依赖文件 |
$* | 匹配通配符%的部分 |
make自动推导
make看到xx.o
,可以自动地推导出它依赖同名的xx.c
,并且gcc -c xx.c
也会被自动加上。
由此,上例又可以省略为:
makefile
OBJ = main.o somec.o
TARGET = myProcess
CC = gcc
$(TARGET) : $(OBJ)
$(CC) -o $(TARGET) $(OBJ)
main.o : somec.h
somec.o : somec.h
clean :
rm $(TARGET) $(OBJ)
包含其他makefile文件
默认情况下,make
会寻找命名为makefile和Makefile的文件,当然也可以是其他任意命名的makefile文件,不过需要使用make -f <filename>
来显式地指定。
在一个makefile中使用include <filenames>
来包含其他makefile文件,
文件搜索
默认情况下,makefile只会在当前目录搜索文件。
- 使用
VPATH
变量指明其他的目录用于搜索。
例如VPATH = src:../headers
,其中src和headers都是搜索目录,它们之间用:
隔开。 - 使用
vpath
关键字。
例如vpath %.h ../headers
表示在headers下搜索所有以.h结尾的文件。
一次生成多个程序
按基本的写法,makefile只会从第一行,即第一个生成程序开始查找依赖,如果后面还有程序要生成则可能被忽略。
可以使用伪目标来达成这一目的:
makefile
all : prog1 prog2
.PHONY : all
prog1 : prog1.o
gcc -o prog1 prog1.o
prog2 : prog2.o
gcc -o prog2 prog2.o
声明一个伪目标all,它依赖于两个程序,但由于伪目标不会被生成文件,因此all总是需要更新其依赖,也就总是生成这多个程序。
条件判断
使用ifeq, ifneq, ifdef, ifndef
来进行条件判断,并且都以endif
结尾。
make读取Makefile时就计算条件表达式的值,并根据其值来选择语句,所以,不要把自动化变量放入条件表达式中,因为自动化变量是在运行时才有的。
函数
格式为$(function argument1,argument2)
。函数和参数以空格隔开,参数见以逗号隔开。
下面介绍一些常用的函数。
字符串函数
- subst
$(subst from,to,text)
,把字符串text中的from替换为to。 - patsubst
$(patsubst pattern,replacement,text)
,模式替换,把text中的单词pattern替换为replacement。 - strip
$(strip string)
,去空格 - filter
$(filter pattern,text)
,只保留text中符合pattern模式的单词。
例如```
makefile
sources := a.c b.o c.s
$(filter %.c %.o,$(sourecs))
返回a.c b.o
- sort
$(sort string)
,给string中的单词排序,注意会去重。 - word
$(word n,string)
,取string中的第n个单词。 - words
$(words string)
,统计单词个数。
文件函数
- dir
$(dir names)
,从文件名列表names中取出目录,例如$(dir /src/foo.c temp)
返回/src/ ./
- suffix
$(suffix names)
,从names中取出各个文件的后缀。 - basename
同上,只不过取的是前缀名。
控制流函数
- foreach
$(foreach var,list,expr)
,把list中的单词拿出来赋给var,然后执行expr。expr每次的返回会以空格隔开,最后组成整个函数的返回值。
例如:$(foreach n,a b,$(n).o)
返回a.o b.o
。
注意var是一个局部变量,其作用域只在foreach中。 - if
$(if condition,then-part,else-part)
,else-part可以省略。 - shell
$(shell expr)
,执行一个shell命令作为返回值。 - error
$(error text)
,停止make并报错text。 - warning
同上,不过不退出make而是继续执行。
总结
GNU make的执行步骤如下:
- 读入所有的Makefile。
- 读入被include的其它Makefile。
- 初始化文件中的变量。
- 推导隐式规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
第三步并非一开始就将所有变量展开,而是其出现在依赖关系中且要被使用了才展开。