Makefile 入门
make
是软件开发中常用的工具程序(Utility software),常被用来构建C程序。make
通过读取叫做“makefile”的文件,来自动化建构软件。
虽然
make
常被用来构建C程序,但它可用于绝大多数的文件处理过程。例如,更新一批图片的缩略图: http://harttle.github.io/2013/10/26/auto-thumb/
它是一种转化文件形式的工具,转换的目标称为“target”;
与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。
它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。
大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。
它使用叫做Makefile
的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。
基本知识
编译
对于C语言,产生可执行程序包括这样的步骤:
- 预处理源文件(
.c
)- 替换预处理命令(如
#define
) - 展开头文件(
.h
,包括静态链接库的头文件)到引用的源文件
- 替换预处理命令(如
- 依次编译处理过的源文件,然后进行汇编,生成对应的目标文件(
.o
) - 链接(静态链接)目标文件和静态链接库(静态链接库的源文件生成的目标文件,
.a
),生成可执行的二进制文件。
gcc
不会自动链接到静态链接库(libstdio.so
除外),需要以参数的形式指定。g++
会自动解析并进行链接。动态链接库(.so
)则是运行时由操作系统解析的。
示例
target: require1 require2
shell command
make
命令会寻找当前目录下的 makefile,寻找其中的第一条规则,然后执行 shell command
来生成 target
。如果发现 requireN
同时也依赖于其他项,则会自动调用其对应的 shell command
并生成它。另外,在整个过程中,make
会比较 target
与 requireN
的时间戳,以判断是否需要重新生成。
变量
变量的定义与引用:
# 递归扩展变量,在调用时展开
objects1 = a.o b.o
# 简单扩展变量,在赋值时展开
objects2 = $(objects) c.o
main.o: $(objects)
gcc -o main.o $(objects2)
-
如果要在makefile中真实的
$
,需要用$$
来表示。 -
注意变量赋值时不能使用通配符。但是在规则中,通配符是可以使用的:
print: *.c some command
自动化变量
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@
就是匹配于目标中模式定义的集合。$%
当目标是函数库文件时,表示规则中的目标成员名。例如,如果一个目标是foo.a (bar.o)
,那么,$%
就是bar.o
,$@
就是foo.a
。如果目标不是函数库文件(Unix 下是.a
,Windows 下是.lib
),那么,其值为空。$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即%
)定义的,那么$<
将 是符合模式的一系列的文件集。$?
所有比目标新的依赖目标的集合。以空格分隔。$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量 会去除重复的依赖目标,只保留一份。$+
这个变量很像"$^"
,也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*
这个变量表示目标模式中%
及其之前的部分。如果目标是dir/a.foo.b
,并且目标的模式是a.%.b
,那么,$*
的值就是dir/a.foo
。
貌似共有22个自动化变量,详情请查询:https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
隐含规则
make
会通过隐含规则自动推导依赖关系,可以帮助我们简化 makefile
。例如:
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
并没有指明如何生成 .o
文件,这时 make
会生成:
foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
隐含规则中的编译器与上一级依赖中的相同,因此产生 foo
的规则中,cc
命令是不可省略的。
使用函数
可以使用 make
内建函数来完成某些功能。语法如下:
$(<function> <arguments>)
如:
sources := $(shell ls *.c)
make
中的函数包括:字符串处理,文件名操作,流程控制,call函数,origin函数,shell函数。
多条命令
在同一Make规则中可以执行多条命令,如果第一条出错后续的命令都得不到执行。最典型的情景:
build: foo
mkdir build
cp foo build/
当build/
目录已经存在时mkdir build
会返回false
,于是cp
得不到执行。
为了让cp
总可以执行,可以简单地在mkdir build
前加一个-
来消除这个错误对make的影响:
build: foo
-mkdir build
cp foo build/
禁止输出指令
Make在执行每条指令前都会先输出该指令,在命令前添加@
可以禁止该行为:
build: foo
@cp foo build/foo
这样cp foo build/foo
就不会被输出了。
进阶技巧
伪目标
伪目标不是一个文件,而只是一个标签,使得依赖关系的书写更加灵活。.PHONY
用来声明伪目标(但不是必须的),这时可以避免 make 去检查同名的文件。例如:
.PHONY: clean
clean:
rm *.o temp
多目标
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
# 等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
静态模式
静态模式可以更加容易地定义多目标的规则,语法如下:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
例如,下面的makefile将对所有的 .o
文件分别生成一条依赖关系。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
相当于:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
总控 makefile
在makefile中,可以执行子文件夹的makefile:
subsystem:
$(MAKE) -C subdir
这里的$(MAKE)
是为了方便维护make的参数。总控makefile的变量可以传递到下级:
export variable = value
# 或者
variable = value
export variable
自动生成依赖
对于每个 .c
文件,我们一般会需要如下的依赖关系:
main.o : main.c defs.h
然而,有时候存在大量的 .c
文件,我们需要得到每个 .c
对应的 .h
。借助于 gcc
,可以自动探测这些依赖:
sources = main.c stack.c maze.c
-include $(sources:.c=.d)
%.d: %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
如上的makefile定义了 .c
的列表变量,然后载入对应的 .d
文件,这时会有文件不存在的警告产生,此时 make 会采用下面的模式生成 .d
:
- 设置shell,发生错误立即退出
- 得到依赖关系(如
main.o main.c defs.h
),存为.d.XXXX
(XXXX
为当前进程号) sed
替换(例如将main.o : main.c defs.h
转成main.o main.d : main.c defs.h
),使得.d
也能得到更新。- 得到的新规则存为
.d
,然后删除临时文件 转载自 Harttle.Land
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可。转载请注明来源: https://snowfrs.com/2014/01/01/makefile.html 欢迎对文中引用进行考证,欢迎指出任何不准确和模糊之处。