make 备忘清单

包含 最重要概念、函数、方法等的 make 备忘单。 初学者的完整快速参考。

Makefile 入门

示例

a.txt: b.txt c.txt
	cat b.txt c.txt > a.txt

工作流程

  • 读入所有的 Makefile
  • 读入被 include 的其它 Makefile
  • 初始化文件中的变量。
  • 推导隐晦规则,并分析所有规则。
  • 为所有的目标文件创建依赖关系链。
  • 根据依赖关系,决定哪些目标要重新生成。
  • 执行生成命令。

文件命令

文件会以 GNUmakefile(不推荐使用)、Makefilemakefile 查找目录下的名称。

自定义文件名称

$ make target -f NAME

我们可以使用 -f NAME 来指定需要编译的文件名

隐式生成

如果文件夹中没有 makefile 文件,只有 main.c 源文件,那么我们可以使用 make main.o 隐式生成链接文件

$ make main.o
# 实际执行: cc    -c -o main.o main.c

规则

TARGET: PREREQUISITES
  COMMAMD
...
  • target: 规则的目标。目标可以是规则的动作(如 clean 等),也可以是目标文件或者最后的可执行文件。
  • prerequisites: 规则的依赖。生成规则目标文件所需要的文件名列表(通常一个目标依赖于一个或者多个文件)。
  • command: 规则的命令行。规则要执行的动作(任意的 shell 命令或者在 shell 下执行的程序)。命令需要以 tab 键开头
$ make [TARGET ...]

$ make        # 没有参数首先运行 TARGET
$ make help   # 显示可用目标
$ make dist   # 从当前目录制作一个发布存档
$ make check  # 无需安装的单元测试

清空目标文件

.PHONY: clean
clean:
  rm *.o temp

.PHONY 内置命令将排除 clean 文件,不会因为当前目录中因为有 clean 文件而不会不执行 clean 伪目标

clean 从来都是放在文件的最后

注释

makefile 文件的注释与 bash 脚本一致

# 这是一个注释
main.o : main.c
	cc -c main.c

换行 \

# 这是一个注释
main.o : main.c
	cc -c \
	main.c

引用其它的 Makefile

include 关键字可以把别的 Makefile 包含进来。这样使用 make 运行的时候就会

# makefile
include foo.make

如果你想让 make 不理那些无法读取的文件,并且继续执行。

-include <filename>

变量

赋值符

在执行时扩展,允许递归扩展

VARIABLE = value

在声明时扩展

可以防止递归,并且只能引用之前声明过的变量

VARIABLE := value

只有在该变量为空时才设置值

VARIABLE ?= value

将值追加到变量的尾端

VARIABLE += value

override

如果变量前不指定 override,那么命令行中指定的变量可以对 Makefile 中的变量重新定义。

# 不会重新定义
override VARIABLE = value
override VARIABLE := value
override VARIABLE ?= value
override VARIABLE += value
override define
  #...
endef

变量

需要使用 $() 或者 ${} 对变量进行引用

file = main.c

run:
	clang -o hello ${file}

避免递归变量

# 这样会使变量陷入无穷递归
A = $(B)
B = $(A)

x := foo
y := $(x) bar
x := later

# 等价于
# x = later
# y = foo bar

Shell 变量

如果要使用 Shell 变量,需要在之前加上 $

run:
	echo $$HOME

定义多行变量

define foo
echo foo
echo bar
endef

run:
	${foo}

自动变量

$@

$@:指代当前目标,即 Make 命令当前构建的那个目标

foo:
	touch $@

$ make foo
# $@ 就是指的这里的 foo

$<

$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么 $< 就指代 p1

a.md: b.md c.md
  cp $< $@

使用 make a.md,相当于以下写法

a.md: b.md c.md
    cp b.md a.md 

$^

$^ 指代所有的前置条件,去除重复项

a.md: b.md c.md
  echo $^

$+

$^ 指代所有的前置条件,不会去除重复项

a.md: b.md c.md c.md
	echo $+

$?

$? 指代更新的依赖,只有最近更新过的依赖才会引用

a.md: b.md c.md c.md
	cat $? > a.md

$*

$* 指代匹配符匹配的部分

main.o: main.c
	clang -o $* $*.c

$ make main
# 此时 cc  main.c -o main

$%

$%: 仅当目标是函数库文件中,表示规则中的目标成员名

  • windows 中是 .lib 文件
  • unix 中是 .a 文件

内置命名变量的参数

这些变量都是相关下面的命令的参数。如果没有指明其默认值,那么其默认值都是空。

:-:-
ARFLAGS函数库打包程序AR命令的参数。默认值是 rv
ASFLAGS汇编语言编译器参数。(当明显地调用 .s.S 文件时)
CFLAGSC 语言编译器参数。
CXXFLAGSC++ 语言编译器参数。
COFLAGSRCS 命令参数。
CPPFLAGSC 预处理器参数。( CFortran 编译器也会用到)。
FFLAGSFortran 语言编译器参数。
GFLAGSSCCS get 程序参数。
LDFLAGS链接器参数。(如:ld
PFLAGSPascal 语言编译器参数。
LFLAGSLex 文法分析器参数。
RFLAGSRatfor 程序的 Fortran 编译器参数。
YFLAGSYacc 文法分析器参数。

内置已命名的变量

:-:-
AR函数库打包程序。默认命令是 ar
AS汇编语言编译程序。默认命令是 as
CCC 语言编译程序。默认命令是 cc
CXXC++ 语言编译程序。默认命令是 g++
CO从 RCS 文件中扩展文件程序。默认命令是 co
CPPC 程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
FCFortran 和 Ratfor 的编译器和预处理程序。默认命令是 f77
GET从 SCCS 文件中扩展文件的程序。默认命令是 get
LEXLex 方法分析器程序(针对于 C 或 Ratfor)。默认命令是 lex
PCPascal 语言编译程序。默认命令是 pc
YACCYacc 文法分析器(针对于 C 程序)。默认命令是 yacc
YACCRYacc 文法分析器(针对于 Ratfor 程序)。默认命令是 yacc –r
MAKEINFO转换 Texinfo 源文件(.texi)到 Info 文件程序。默认命令是 makeinfo
TEX从 TeX 源文件创建TeX DVI文件的程序。默认命令是 tex
TEXI2DVI从 Texinfo 源文件创建 TeX DVI 文件的程序。默认命令是 texi2dvi
WEAVE转换 Web 到 TeX 的程序。默认命令是 weave
CWEAVE转换 C Web 到 TeX 的程序。默认命令是 cweave
TANGLE转换 Web 到 Pascal 语言的程序。默认命令是 tangle
CTANGLE转换 C Web 到 C。默认命令是 ctangle
RM删除文件命令。默认命令是 rm –f

内置的变量

run:
	${CC} -o main main.c

书写规则

通配符

*

*:与 linux 系统下的一样

# 清除所有 .o 结尾的文件
clean:
    rm -f *.o

~

~:在 linux 或 mac 下表示用户目录,win 下表示 HOME 环境变量

run:
    ls ~

?

?: 与在 linux 等类似,可以匹配单个字符

run:
	ls -ll packag?.json

文件搜寻(vpath

如果没有指定 vpath 变量,make 只会在当前的目录中去寻找依赖文件和目标文件。否则,如果当前目录没有,就会到指定的目录中去寻找

:-:-
vpath <pattern> <directories>为符合模式 <pattern> 的文件指定搜索目录 <directories>
vpath <pattern>清除符合模式的文件的搜索目录。
vpath清除所有已被设置好了的文件搜索目录

%

  • vpath使用方法中的 <pattern> 需要包含 % 字符。
  • % 的意思是匹配零或若干字符
  • 并且引用规则是需要使用自动变量
vpath %.c dist
TARGET = hello
OBJ = bar.o foo.o

$(TARGET): $(OBJ)
	$(CC) -o $@ $^

%.o: $.c
	$(CC) -o $< -o #@

静态模式

TARGET: PREREQUISITES :PREREQUISITES
  COMMAMD
#...
  • target 定义了一系列的目标文件
  • 第一个 prerequisites 是指明了 target 的模式,也就是的目标集模式。
  • 第二个 prerequisites 是目标的依赖模式,它对第一个 prerequisites 形成的模式再进行一次依赖目标的定义
objects = foo.o main.o

$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

相当于:

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
main.o : main.c
    $(CC) -c $(CFLAGS) main.c -o main.o

伪目标

  • 伪目标并不是一个文件,只是一个标签。只有通过显式地指明这个目标才能让其生效
  • 使用 .PHONY 来显式地指明目标是 伪目标
.PHONY : clean
clean :
    rm *.o temp

命令

回声(@

正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)

all:
  # 会有命令执行显示
	echo Hello, world

all:
  # 不会有命令执行的显示
	@echo Hello, world

显示命令、禁止命令

显示命令

如果我们只希望显示命令,而不希望执行命令,可以使用 -n 或者 --just-print

$ make all --just-print 
$ make all -n 

禁止命令

-s--silent--quiet@ 一样,用于禁止回声

$ make all -s 

执行命令

使用 tab 及换行

exec:
    cd /home/hchen
    pwd

使用 ;

exec:
    cd /home/hchen; pwd