Makefile 简明手册 .

一个完整的 Makefile 通常由 "显式规则"、"隐式规则"、"变量定义"、"指示符"、"注释" 五部分组成。

  • 显式规则: 描述了在何种情况下如何更新一个或多个目标文件。
  • 隐式规则: make 默认创建目标文件的规则。(可重写)
  • 变量定义: 类似 shell 变量或 C 宏,用一个简短名称代表一段文本。
  • 指示符: 包括包含(include)、条件执行、宏定义(多行变量)等内容。
  • 注释: 字符 "#" 后的内容被当作注释。

(1) make 会在工作目录按照 "GNUmakefile、makefile、Makefile(推荐)" 顺序查找并执行,或使用 "-f" 参数显式指定文件名。
(2) 如果不在 make 命令行显式指定目标规则名,则默认使用第一个有效规则。
(3) Makefile 中 "$"、"#" 有特殊含义,可以进行转义 "/#"、"$$"。
(4) 可以使用 "/" 换行(注释行也可以使用),但其后不能有空格,新行同样必须以 Tab 开头和缩进。

注意: 本文中提到的目标文件通常是 ".o",类似的还有源文件(.c)、头文件(.h) 等。

1. 规则

规则组成方式:

target...: prerequisites...
    command
    ...
  • target: 目标。
  • prerequisites: 依赖列表。一个或多个文件名列表(空格分隔,通常是 ".o, .c, .h" 等,可以使用通配符)。
  • command: 命令行。shell 命令或程序,且必须以 Tab 开头(最容易犯的错误)。

没有命令行的规则只能指示 "依赖关系",没有依赖项的规则指示 "如何" 构建目标,而非 "何时" 构建。
目标的依赖列表可以通过 GCC -MM 参数获得。

规则处理方式:

(1) 目标文件不存在,使用其规则(显式或隐式规则)创建。
(2) 目标文件存在,但如果任何一个依赖文件比目标文件修改时间 "新",则重新创建目标文件。
(3) 目标文件存在,且比所有依赖文件 "新",则什么都不做。

1.1 隐式规则

当我们不编写显式规则时,隐式规则就会生效。当然我们可以修改隐式规则的命令。

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


未定义规则或者不包含命令的规则都会使用隐式规则。

# 隐式规则
%.o: %.c
    @echo $<
    @echo $^
    $(CC) $(CFLAGS) -o $@ -c $<

all: test.o main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

main.o: test.o test.h


输出:

$ make

./lib/test.c
./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c

./src/main.c
./src/main.c test.o ./lib/test.h
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c

gcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o


"test.o" 规则不存在,直接使用了隐式规则。"main.o" 没有命令,使用隐式规则的同时,还会合并依赖列表。

1.2 模式规则

在隐式规则前添加特定的目标,就形成了模式规则。

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


1.3 搜索路径

在实际项目中我们通常将源码文件分散在多个目录中,将这些路径写入 Makefile 会很麻烦,此时可以考虑用 VPATH 变量指定搜索路径。

all: lib/test.o src/main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^


改写成 VPATH 方式后,要调整项目目录就简单多了。

# 依赖目标搜索路径
VPATH = ./src:./lib

# 隐式规则
%.o:%.c
    -@echo "source file: $<"
    $(CC) $(CFLAGS) -o $@ -c $<

all:test.o main.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^


执行:

$ make

source file: ./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c

source file: ./src/main.c
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c

gcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o


还可使用 make 关键字 vpath。比 VPATH 变量更灵活,甚至可以单独为某个文件定义路径。

vpath %.c ./src:./lib # 定义匹配模式(%匹配任意个字符)和搜索路径。
vpath %.c # 取消该模式
vpath # 取消所有模式


相同的匹配模式可以定义多次,make 会按照定义顺序搜索这多个定义的路径。

vpath %.c ./src
vpath %.c ./lib
vpath %.h ./lib


1.4 伪目标

当我们为了执行命令而非创建目标文件时,就会使用伪目标了,比如 clean。伪目标总是被执行。

clean:
    -rm *.o
    
.PHONY: clean


使用 "-" 前缀可以忽略命令错误,".PHONY" 的作用是避免和当前目录下的文件名冲突(可能引发隐式规则)。

2. 命令

每条命令都在一个独立 shell 环境中执行,如希望在同一 shell 执行,可以用 ";" 将命令写在一行。

test:
    cd test; cp test test.bak


提示: 可以用 "/" 换行,如此更美观一些。

默认情况下,多行命令会顺序执行。但如果命令出错,默认会终止后续执行。可以添加 "-" 前缀来忽略命令错误。另外还可以添加 "@" 来避免显示命令行本身。

all: test.o main.o
    @echo "build ..."
    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^


执行其他规则:

all: test.o main.o
    $(MAKE) info
    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^

info:
    @echo "build..."


3. 变量

Makefile 支持类似 shell 的变量功能,相当于 C 宏,本质上就是文本替换。
变量名区分大小写。变量名建议使用字母、数字和下划线组成。引用方式 "$(var)" 或 "${var}"。
引用未定义变量时,输出空。

3.1 变量定义

首先注意的是 "=" 和 ":=" 的区别。

  • "=": 递归展开变量,仅在目标展开时才会替换,也就是说它可以引用在后面定义的变量。
  • ":=": 直接展开变量,在定义时就直接展开,它无法后置引用。
A = "a: $(C)"
B := "b: $(C"
C = "haha..."

all:
    @echo $A
    @echo $B


输出:

$ make

a: haha...
b:


由于 B 定义时 C 尚未定义,所以直接展开的结果就是空。修改一下,再看。

C = "none..."
A = "a: $(C)"
B := "b: $(C)"
C = "haha..."

all:
    @echo $A
    @echo $B


输出:

$ make

a: haha...
b: none...


可见 A 和 B 的展开时机的区别。

除了使用 "="、":=" 外,还可以用 "define ... endef" 定义多行变量(宏,递归展开,只需在调用时添加 "@" 即可)。

define help
    echo ""
    echo "  make release : Build release version."
    echo "  make clean   : Clean templ files."
    echo ""
endef

debug:
    @echo "Build debug version..."
    @$(help)
    @$(MAKE) $(OUT) DEBUG=1

release:
    @echo "Build release version..."
    @$(help)
    @$(MAKE) clean $(OUT)


3.2 操作符

"?=" 表示变量为空或未定义时才进行赋值操作。

A = "a"

A ?= "A"
B ?= "B"

all:
    @echo $A
    @echo $B


输出:

$ make

a
B


"+=" 追加变量值。注意变量展开时机。

A = "$B"
A += "..."
B = "haha"

all:
    @echo $A


输出:

$ make

haha ...


3.3 替换引用

使用 "$(VAR:A=B)" 可以将变量 VAR 中所有以 "A" 结尾的单词替换成以 "B" 结尾。

A = "a.o b.o c.o"

all:
    @echo $(A:o=c)


输出:

$ make

a.c b.c c.o


3.4 命令行变量

命令行变量会替换 Makefile 中定义的变量值,除非使用 override。

A = "aaa"
override B = "bbb"
C += "ccc"
override D += "ddd"

all: 
    @echo $A
    @echo $B
    @echo $C
    @echo $D


执行:

$ make A="111" B="222" C="333" D="444"

111
bbb
333
444 ddd


我们注意到追加方式在使用 override 后才和命令行变量合并。

3.5 目标变量

仅在某个特定目标中生效,相当于局部变量。

test1: A = "abc"

test1:
    @echo "test1" $A

test2:
    @echo "test2" $A


输出:

$ make test1 test2

test1 abc
test2


还可以定义模式变量。

test%: A = "abc"

test1:
    @echo "test1" $A

test2:
    @echo "test2" $A


输出:

$ make test1 test2

test1 abc
test2 abc


3.5 自动化变量

  • $?: 比目标新的依赖项。
  • $@: 目标名称。
  • $<: 第一个依赖项名称(搜索后路径)。
  • $^: 所有依赖项(搜索后路径,排除重复项)。

3.6 通配符

在变量定义中使用通配符则需要借助 wildcard。

FILES = $(wildcard *.o)

all:
    @echo $(FILES)


3.7 环境变量

和 shell 一样,可以使用 "export VAR" 将变量设定为环境变量,以便让命令和递归调用的 make 命令能接收到参数。

例如: 使用 GCC C_INCLUDE_PATH 环境变量来代替 -I 参数。

C_INCLUDE_PATH := ./lib:/usr/include:/usr/local/include
export C_INCLUDE_PATH
4. 条件

没有条件判断是不行滴。
CFLAGS = -Wall -std=c99 $(INC_PATHS)
ifdef DEBUG
    CFLAGS += -g
else
    CFLAGS += -O3
endif

类似的还有: ifeq、ifneq、ifndef
格式: ifeq (ARG1, ARG2) 或 ifeq "ARG1" "ARG2"
# DEBUG == 1
ifeq "$(DEBUG)" "1"
    ...
else
    ...
endif

# DEBUG 不为空
ifneq ($(DEBUG), )
    ...
else
    ...
endif

实际上,我们可以用 if 函数来代替。相当于编程语言中的三元表达式 "?:"。
CFLAGS = -Wall $(if $(DEBUG), -g, -O3) -std=c99 $(INC_PATHS)

5. 函数

*nix 下的 "配置" 都有点 "脚本语言" 的感觉。
make 支持函数的使用,调用方法 "$(function args)" 或 "${function args}"。多个参数之间用 "," (多余的空格可能会成为参数的一部分)。

例如: 将 "Hello, World!" 替换成 "Hello, GNU Make!"。
A = Hello, World!
all:
    @echo $(subst World, GUN Make, $(A))

注意: 字符串没有用引号包含起来,如果字符串中有引号字符,使用 "/" 转义。

5.1 foreach

这个 foreach 很好,执行结果输出 "[1] [2] [3]"。
A = 1 2 3
all:
    @echo $(foreach x,$(A),[$(x)])

5.2 call

我们还可以自定义一个函数,其实就是用一个变量来代替复杂的表达式,比如对上面例子的改写。
A = x y z
func = $(foreach x, $(1), [$(x)])
all:
    @echo $(call func, $(A))
    @echo $(call func, 1 2 3)

传递的参数分别是 "$(1), $(2) ..."。

用 define 可以定义一个更复杂一点的多行函数。
A = x y z
define func
    echo "$(2): $(1) -> $(foreach x, $(1), [$(x)])"
endef

all:
    @$(call func, $(A), char)
    @$(call func, 1 2 3, num)

输出:
$ make

 char:  x y z ->  [x]  [y]  [z]
 num:  1 2 3 ->  [1]  [2]  [3]

5.3 eval

eval 函数的作用是动态生成 Makefile 内容。
define func
    $(1) = $(1)...
endef

$(eval $(call func, A))
$(eval $(call func, B))

all:
    @echo $(A) $(B)

上面例子的执行结果实际上是 "动态" 定义了两个变量而已。当然,借用 foreach 可以更紧凑一些。
$(foreach x, A B, $(eval $(call func, $(x))))

5.4 shell

执行 shell 命令,这个非常实用。
A = $(shell uname)
all:
    @echo $(A)

更多的函数列表和详细信息请参考相关文档。

6. 包含

include 指令会读取其他的 Makefile 文件内容,并在当前位置展开。
通常使用 ".mk" 作为扩展名,支持文件名通配符,支持相对和绝对路径。

7. 执行

Makefile 常用目标名:
  • all: 默认目标。
  • clean: 清理项目文件的伪目标。
  • install: 安装(拷贝)编译成功的项目文件。
  • tar: 创建源码压缩包。
  • dist: 创建待发布的源码压缩包。
  • tags: 创建 VIM 使用的 CTAGS 文件。
make 常用命令参数:
  • -n: 显示待执行的命令,但不执行。
  • -t: 更新目标文件时间戳,也就是说就算依赖项被修改,也不更新目标文件。
  • -k: 出错时,继续执行。
  • -B: 不检查依赖列表,强制更新目标。
  • -C: 执行 make 前,进入特定目录。让我们可以在非 Makefile 目录下执行 make 命令。
  • -e: 使用系统环境变量覆盖同名变量。
  • -i: 忽略命令错误。相当于 "-" 前缀。
  • -I: 指定 include 包含文件搜索目录。
  • -p: 显示所有 Makefile 和 make 的相关参数信息。
  • -s: 不显示执行的命令行。相当于 "@" 前缀。
顺序执行多个目标:
$ make clean debug
 
原文见:http://blog.csdn.net/unbutun/article/details/6573712


对“info make”的翻译整理,不是一个纯粹的语言翻译版本,其中对GNU make的一些语法和用法进行了一些详细分析和说明,也加入了一些个人的观点和实践总结。 本书的所有的例子都可以在支持V3.8版本的GNU make的系统中正确执行。 中文于册 伪目标 强制目标(没有命令或依赖的规则) 空目标文件 的特殊目标 多目标 多规则目标 静态模式 静态模式规则的语法 静态模式和隐含规则 双冒号规则 自动产生依赖 第五章:规则的命令 为规则书写命令 命令回显 命令的执行 并发执行命令 命令执行的错误 中断的执行 的递归执行 变量 变量和递归 命令行选项和递归 选项 定义命令包 第六章 中的变量 使用变量 变量的引用 两种变量定义(赋值) 归展开式变量 直接展开式变量 定义一个空格 ”操作符 变量的高级用法 变量的替换引用 变量的套嵌引用 变量取值 如何设置变量 追加变量值 指示符 多行定义 系统环境变量 目标指定变量 模式指定变量 第七章 的条件执行 的条件判断 个例子 条件判断的基本语法 标记测试的条件语句 笫八章:的内嵌函数 的函数 年月日 中文于册 函数的调用语法 文本夂理函数 文件名处理函数 函数 函数 西数 函数 函数 函数 西数 的控制函数 第九章:执行 执行 指定 文件 指定终极日标 替代命令的执行 防止特定文件重建 替换变量定义 使用 进行编译测试 的命令行选项 第十章: 的隐含规则 使用隐含规则 隐含规则的使用 的隐含规则一览 隐含变量 代表命令的变量 命令参数的变量 隐含规则链 模式规 模式规则介绍 模式规则示例 自动化变量 年月日 中文于册 模式的匹配 万用规则 重建内嵌隐含规则 缺省规则 后缀规则 隐含规则搜索算法 笫十一章:使用更新静态库文件 更新静态库文件 库成员作为目标 静态库的更新 更新静态庠的符号索引表 静态库的注意享项 静态库的后缀规则 第十二章: 的特点 的一些特点 源自 的特点 源自其他版本的特点 自身的特点 第十三章和其它版本的兼容 不兼容性 第十四章 的约定 书写约定 基本的约定 规则命令行的约定 代表命令变量 安装目录变量 的标准目标名 安装命令分类 第十五章的常见错误信息 产生的错误信息 附录:关键字索引 可识别的指示符 函数 的自动化变量 环境变量 后序 年月日 中文于册 关于本书 本文瑾献给所有热爱 的程序员!本中文文档版权所有 本文比较完整的讲述 工具,涵盖 的用法、语法。同时重 讨论如何为一个工程编写 作为一个程序员, 工具的使用以及编 写 是必嚅的。系统、详细讲述的中文资料比较少,出于对广大中文 的支持,本人在工作之余,花了个多月时间完成对“ 的翻译整理,完成 这个中文版手册。夲书不是一个纯粹的语言翻译版本,其中对 的一些语法 和用法根据我个人的工作经验进行了一些详细分析和说明,也加入了一些个人的观点和 实践总结。本书的所有的例子都可以在支持版本的 的系统中正确执行。 由于个人水平限制,本文在一些地方存在描述不准确之处。恳请大家在阅读过程中 提出您宝贵的意见,也是对我个人的帮助。我的个人电子邯箱地址: 非常愿意和大家交流!共同学习 阅读本书之前,读者应该对 的工具链和 的一些常用编程工具有一定的 了解。诸如: 等;同时在书写 时,需要能够进行一些 基本的编程。这些工具是维护一个工程的基础。如果大家对这些工具的用法不是 很熟悉,可参考项目资料 阅读本文的几点建议: 如果之前你对 没有了解、当前也不想深入的学习 的读 者。可只阅读本文各章节前半部分的内容(作为各章节的基础知识) 如果你已经对 比较熟悉,你更霄要关心此版本的新增特点、功能、 和之前版本不兼容之处;也可以作为开发过程过程的参考手册。 之前你对 没有概念、或者刚开始接触,本身又想成为一个 下 的专业程序员,那么建议:完整学习本文的各个章节,包括了基础知识和高级 用法、技巧。它会为你在 下的工程开发、工程管理提供非常有用的帮助。 此中文文档当前版本 本文的所有勘误和最新版本可在主 页 上获取!! 谢谢! 徐海兵 年月日 中文于册 第一章:概述 概既述 环境下的程序员如果不会侠用 来构建和管理自己的工程,应该 不能算是一个合柊的专业程序员,至少不能称得上是程序员。在 )环 境下侠用 的 工具能够比较容易的构建一个属于你自己的工程,整个工程的 编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入 些时间去完成一个或者多个称之为 文件的编写。此文件正是 正常工作 的基础 所要完成的 文件描述了整个工程的编译、连接等规则。其中包括:工程 中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文 件、如何最后产生我们想要得可执行文件。尽管看起来可能是很复杂的事情,但是为工 程编写
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值