make 是软件开发中常用的工具程序(Utility software),常被用来构建 C 程序。make 通过读取叫做“makefile”的文件,来自动化建构软件。

虽然 make 常被用来构建 C 程序,但它可用于绝大多数的文件处理过程。例如,批量更新网站的缩略图

它是一种转化文件形式的工具,转换的目标称为“target”; 与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。 它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。 大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。 它使用叫做 Makefile 的文件来确定一个 target 文件的依赖关系,然后把生成这个 target 的相关命令传给 shell 去执行。

中文 Tutorial

基本知识

编译

对于 C 语言,产生可执行程序包括这样的步骤:

  1. 预处理源文件(.c
    • 替换预处理命令(如 #define
    • 展开头文件(.h,包括静态链接库的头文件)到引用的源文件
  2. 依次编译处理过的源文件,然后进行汇编,生成对应的目标文件(.o
  3. 链接(静态链接)目标文件和静态链接库(静态链接库的源文件生成的目标文件,.a),生成可执行的二进制文件。

gcc 不会自动链接到静态链接库(libstdio.so 除外),需要以参数的形式指定。g++ 会自动解析并进行链接。动态链接库(.so)则是运行时由操作系统解析的。

示例

target: require1 require2
    shell command

make 命令会寻找当前目录下的 makefile,寻找其中的第一条规则,然后执行 shell command 来生成 target。如果发现 requireN 同时也依赖于其他项,则会自动调用其对应的 shell command 并生成它。另外,在整个过程中,make 会比较 targetrequireN 的时间戳,以判断是否需要重新生成。

变量

变量的定义与引用:

# 递归扩展变量,在调用时展开
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.XXXXXXXX 为当前进程号)
  • sed 替换(例如将 main.o : main.c defs.h 转成 main.o main.d : main.c defs.h),使得 .d 也能得到更新。
  • 得到的新规则存为 .d,然后删除临时文件

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2014/01/01/makefile.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。