Makefile 备忘录

Makefile 备忘录

网上收集的关于 Makefile 资料的整理。

文件格式

# 注释前面不能有制表符,但可以有空格
目标:依赖
[制表符]命令

目标可以是文件名,也可以是伪目标,前面不能有制表符,但可以有空格。多个相同规则的目标可以写在一起。

命令是 Shell 命令,前面必须有制表符,制表符后面可以有空格。命令可以使用反斜线 \ 折行。多条命令分别在不同的 Shell 环境中执行,所以不共享环境变量。要想共享环境变量,可以将多条命令合并成一条命令,中间用分号分隔。命令前面可以添加 @ 符号,表示不回显(即不显示要执行的命令本身,只显示执行结果)。命令前面可以添加 - 字符,表示即使命令执行出错,也继续执行。

make 会比较目标文件和依赖文件的修改日期,如果依赖文件比目标文件新,或目标文件不存在,则 make 会执行该目标所定义的命令。

执行过程

执行 make 命令后,make 程序会在当前目录中查找 Makefile 文件,找到后,会执行第一条规则,也可以在 make 命令后面指定目标名称,从而执行指定的规则。如果要指定特定的 Makefile 文件,可以使用 make -f filename 的格式。

# 由于 hello 这个目标文件不存在,所以执行了该目标所定义的命令。
# 这个 hello 就是一个伪目标,不是一个文件名。
hello:
	@echo "Hello World!"   # 输出 Hello World!

# 通过 make hi 命令执行该目标的规则
hi:
	@echo "Hi Makefile!"   # 输出 Hi Makefile!

如果确实有 hello 这个文件存在,也可以用下面的方法将 hello 声明为伪目标:

# 声明 hello 为伪目标,解析时不進行文件查找。
.PHONY: hello
hello:
	@echo "Hello World!"

可以多次指定依赖项

# hello 依赖 a b 和 c,所以会先执行 a b c 中的命令
hello: a b
hello: c
	@echo "Hello World!"

a:
	@echo "Hello A!"
b:
	@echo "Hello B!"
c:
	@echo "Hello C!"

更新规则

  1. 如果目标没有被编译过,那么目标及其依赖项都要被编译。

  2. 如果目标的某些依赖项被修改,则只编译被修改的依赖项和目标。

  3. 如果某个头文件被修改,则只编译引用了该头文件的目标。

变量

定义变量的方式如下(变量名区分大小写):

# 如果 Value 是一个变量,比如 $(Value),则其值是在 Var 被引用时展开。
# 所以这里相当于 C 中的宏替换(此时 Var 的值不固定,用到 Var 时才去解析其值)
Var = Value

# 如果 Value 是一个变量,比如 $(Value),则其值是在 Var 被定义时展开。
# 所以这里相当于 C 中的常量(此时 Var 的值已固定,定义时就已经解析完毕)
Var := Value

# Var 的值为空时才赋值
Var ?= Value

# Var 的值后面追加 Value 的内容
Var += Value

注意,如果 Value 后面有注释,则注释符号 # 之前的空格也会被算入 Value 的值中,所以尽量不要在变量定义后面添加注释。

可以使用 $()${} 来引用变量,比如 $(Var)${Var}

如果要引用 Shell 中的环境变量,可以使用 $$$${} 来引用,比如 $$HOME$${HOME}。也就是说,在执行命令前,会将命令中的 $$ 替换为 $ 使用,比如 $$? 就表示上个程序的执行结果。

Var = Hello World!
Var += $$HOME

hello:
	@echo $(Var)      # 输出 Hello World! /home/xx

Makefile 有内置变量 $(CC) 表示当前编译器,$(MAKE) 表示当前 make 程序,$(VPATH) 表示依赖项的搜索路径(多个路径之间用冒号分隔)。

hello:
	@echo $(CC)         # 输出 cc
	@echo $(MAKE)       # 输出 make
	@echo $(VPATH)      # 输出空

也可以使用 vpath 命令指定不同类型的依赖文件的搜索路径(百分号匹配零个或多个任意字符):

# 在 ../lib 目录中搜索 *.o 文件
vpath %.o ../lib

# 清除 *.o 文件的搜索路径
vpath %.o

# 清除所有文件的搜索路径
vpath

在命令中可以使用如下几个变量来引用目标和依赖项:

$@ 变量表示当前目标
$^ 变量表示当前目标的所有依赖项
$< 变量表示当前目标的首个依赖项
$? 变量表示比当前目标更新的依赖项
$(?D) 当前目标的目录名
$(?F) 当前目标的文件名
$(<D) 首个依赖项的目录名
$(<F) 首个依赖项的文件名

示例:

Var = Hello World!

hello: a b c
	@echo $@      # 输出 hello
	@echo $^      # 输出 a b c
	@echo $<      # 输出 a

# 为了上面的示例而定义的空目标
a:
b:
c:

如果要覆盖命令行参数中定义的变量,可以使用 override

override Var = Value

include

可以使用 include 关键字引入外部文件:

include Makefile.inc *.mk

hello:
	@echo $(Var)

make 命令中可以添加 -I 参数,指定 include 文件的搜索路径。

通配符

规则中可以使用通配符 *?[a-z][^a-z]。如果要用到真实的 *? 字符,可以使用 \*\? 转义。注意,变量定义中的通配符是不会展开的,如果要展开,可以使用 $(wildcard *.c)

Var := $(wildcard *.c)

hello: *.c
	@echo ${Var}
	@echo $?

多目标

all: a b c

a b c :
	@echo gcc $@.c -o $@

相当于:

all: a b c

a:
	@echo gcc $@.c -o $@
b:
	@echo gcc $@.c -o $@
c:
	@echo gcc $@.c -o $@

静态模式

多个目标 : 目标模式 : 依赖模式
[Tab]命令

示例:

objects = a.o  b.o  c.o

all: $(objects)

# 相当于多条规则,每条规则从 objects 中取出一个条目,
# 然后生成 "%.o : %.c" 目标,命令内容不变。
$(objects) : %.o : %.c
	@echo $@, $^

# 为了上面的示例而定义的空目标
a.c:
b.c:
c.c:

相当于:

objects = a.o  b.o  c.o

all: $(objects)

a.o : a.c
	@echo $@, $^
b.o : b.c
	@echo $@, $^
c.o : c.c
	@echo $@, $^

a.c:
b.c:
c.c:

大多数的 C/C++ 编译器都支持一个 -M 的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如下面的命令(假设 main.c 中有 include "xxx.h"):

cc -M main.c

其输出是:

main.o : main.c xxx.h

如果使用 GNU 的 C/C++ 编译器,则应该使用 -MM 参数,因为 -M 参数会把一些标准库的头文件也包含进来。

子目录

执行所有子目录中的 Makefile 文件:

# 获取当前目录下的所有子目录列表
SUBDIR = $(shell find . -maxdepth 1 -type d)

# 在所有子目录中执行 make
all: $(SUBDIR)
	@for i in $(SUBDIR); do $(MAKE) -C $$i; \
	if test $$? -ne 0; then break; fi; done

# 在所有子目录中执行 make clean
clean: $(SUBDIR)
	@for i in $(SUBDIR); do $(MAKE) clean -C $$i; done

如果要传递变量到下级 Makefile 中,可以使用如下声明:

export Var1 Var2 Var3
# 或
export Var1 = Value

如果要传递所有变量,可以直接使用 export,后面不跟任何内容。

多行变量

define hello
	@echo Hello World!
	@echo Hi world!
endef

all:
	$(hello)

目标特定变量

hello: Var = Hello World!
hello:
	@echo $(Var)

模式变量

Var = Hello World!
%.o: Var = Hello O!

all: a.c  a.o

a.c:
	@echo $(Var)
a.o:
	@echo $(Var)

条件判断

Var = aaa

hello:
# ifeq、ifneq、ifdef、ifndef
ifeq ($(Var), aaa)
	@echo Hello A
else
	@echo Hello B
endif

函数

格式:$(函数名 逗号分隔的参数列表不含空格)

objects = a.o  b.o  c.o
print=@echo $(1)

hello:
	@echo $(objects)
# 字符串替换
	@echo $(objects:.o=.c)                # a.c b.c c.c
	@echo $(subst .o,.c,$(objects))       # a.c b.c c.c
# 模式替换
	@echo $(patsubst %.o,%.c,$(objects))  # a.c b.c c.c
# 去除多余空格
	@echo "   Hello World!   "
	@echo $(strip "   Hello World!   ")   # " Hello World! "
# 判断字符串是否存在
	@echo $(findstring aa,aaa bbb ccc)    # aa
# 过滤
	@echo $(filter a%,a1 a2 b1 b2)        # a1 a2
# 反过滤
	@echo $(filter-out a%,a1 a2 b1 b2)    # b1 b2
# 排序
	@echo $(sort c b d a)                 # a b c d
# 取单词
	@echo $(word 3,a b c d)               # c
# 取连续单词
	@echo $(wordlist 3,5,a b c d e f)     # c d e
# 统计单词个数
	@echo $(words a b c d e f)            # 6
# 取目录
	@echo $(dir ./a.c)                    # ./
# 取文件名
	@echo $(notdir ./a.c)                 # a.c
# 取后缀
	@echo $(suffix ./a.c)                 # .c
# 去后缀
	@echo $(basename ./a.c)               # ./a
# 添加后缀
	@echo $(addsuffix .c,./a.c)           # .././a
# 添加前缀
	@echo $(addprefix ../,./a.c)          # .././a.c
# 链接名称
	@echo $(join aaa bbb,111 222 333)     # aaa111 bbb222 333
# 遍历
	@echo $(foreach i,a b c,$(i).c)       # a.c b.c c.c
# 条件
	@echo $(if 1,true,false)              # true
	@echo $(if 1,true)                    # true
# 获取 Shell 命令的执行结果
	@echo $(shell ls)
	@echo `ls`
# 调用前面定义的 print,传入"Hello World!"
	$(call print,Hello World!)            # Hello World!
# 查询变量源头
# undefined    变量不存在
# default      变量存在,使用内部默认值
# environment  变量在环境变量中定义
# file         变量在 Makefile 文件中定义
# command line 变量在命令行中定义
# automatic    自动化变量
	@echo $(origin Var)                   # undefined
	@echo $(origin objects)               # file
# 生成警告,继续执行
	$(warning warning from user)          # warning from user
# 生成错误
#	$(error abort from user)               # abort by user

隐含规则

假设有 main.cfoo.cfoo.h 三个文件,内容如下:

// main.c
#include "foo.h"
int main() {
    hello();
}
// foo.c
#include <stdio.h>
void hello() {
    printf("Hello World!");
}
// foo.h
void hello();

要将这三个文件编译成 main 程序,只需要下面这个 Makefile 文件即可:

main: foo.o

这里的 main 程序依赖 foo.o,但是并没有指明 foo.o 如何生成。同时也没有指明 main 程序如何生成。这就要用到 make 的隐含规则,它会自动推断 mainfoo.o 的生成规则。

如果存在 foo.c,则会自动生成如下规则:

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

如果存在 foo.cc(或 foo.C,注意后缀是大写),则会自动生成如下规则:

foo.o: foo.cc
	$(CXX) -c foo.cc $(CPPFLAGS) $(CFLAGS)

对于 main 程序,则会自动生成如下规则:

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

所以,如果把上面的 Makefile 文件写完整,就是下面这个样子:

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

该 Makefile 文件还可以写成下面的样子:

main: foo.c

这样就不会生成临时文件 foo.o 了,该 Makefile 文件如果写完整,就是下面的样子:

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

常用伪目标

all 编译所有的目标。

clean 删除所有由 make 创建的文件。

install 安装已编译好的程序,其实就是把目标文件拷贝到指定目录中去。

print 列出改变过的源文件。

tar 把源程序打包备份。

dist 把源程序压缩备份。

TAGS 更新所有的目标,以备完整地重编译使用。

check 和 test 一般用来测试 makefile 的流程。

命令行参数

用法:make [选项] [目标] ...

-b, -m                        忽略和其它版本 make 的兼容性。

-f 文件, --file=文件, --makefile=文件
                              从 <文件> 中读入 makefile。
-I 目录, --include-dir=目录   在 <目录> 中搜索被包含的 makefile。
-E 字串, --eval=字串          将 <字串> 作为 makefile 语句解析。
-e, --environment-overrides   用环境变量覆盖 makefile 中的变量。
-C 目录, --directory=目录     在执行前先切换到 <目录>。

-r, --no-builtin-rules        禁用内置隐含规则。
-R, --no-builtin-variables    禁用内置变量设置。

-s, --silent, --quiet         不输出配方命令。
--no-silent                   对配方进行回显(禁用 --silent 模式)。
-w, --print-directory         打印当前目录。
--no-print-directory          关闭 -w,即使 -w 默认开启。

--warn-undefined-variables    当引用未定义变量的时候发出警告。
-i, --ignore-errors           忽略来自命令配方的错误。
-k, --keep-going              当某些目标无法制作时仍然继续。
-S, --no-keep-going, --stop   关闭 -k。

-n, --just-print, --dry-run, --recon
                              只打印命令配方,不实际执行。
-q, --question                不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是 0 则说明要更新,如果是 2 则说明有错误发生。
--trace                       打印跟踪信息。
-d                            相当于 --debug=a。
--debug[=旗标]                打印各种调试信息,旗标如下:
    a 输出所有的调试信息。
    b 只输出简单的调试信息。即输出不需要重编译的目标。
    v 在 b 选项的级别之上。输出的信息包括哪个 makefile 被解析,不需要被重编译的文件等。
    i 输出所有的隐含规则。
    j 输出执行规则中命令的详细信息,如命令的 PID、返回码等。
    m 输出 make 读取 makefile,更新 makefile,执行 makefile 的信息。

-B, --always-make             无条件 make 所有目标。
-t, --touch                   更新目标文件的修改时间,而不更改它们的内容。
-o 文件, --old-file=文件, --assume-old=文件
                              假定 <文件> 不需要更新(old 表示此文件未发生改动)。
-W 文件, --what-if=文件, --new-file=文件, --assume-new=文件
                              假定 <文件> 需要更新(new 表示此文件发生了改动)。
-L, --check-symlink-times     使用软链接及软链接目标中修改时间较晚的一个。

-j [N], --jobs[=N]            同时允许 N 个任务;无参数表明允许无限个任务。
-l [N], --load-average[=N], --max-load[=N]
                              在系统负载高于 N 时不启动多任务。
-O[类型], --output-sync[=类型]
                              使用 <类型> 方式同步并行任务输出。

-h, --help                    打印该帮助信息并退出。
-v, --version                 打印 make 的版本号并退出。
-p, --print-data-base         打印 make 的内部数据库。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值