找回密码
立即注册
搜索
发回帖 发新帖

5458

积分

0

好友

704

主题
发表于 昨天 23:18 | 查看: 9| 回复: 0

Makefile 是 C/C++ 工程中绕不开的构建工具。很多人平时只会在模板上改一改,遇到复杂一点的变量赋值、函数调用就容易卡住。本文整理了一份常见 Makefile 规则和函数用法,适合当作速查手册,需要时翻出来对照着写。

01 Makefile 通用规则

一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:

目标(target):依赖(dependings)
<tab>命令(command)
  • 目标(target):通常是要生成的文件的名称,可以是可执行文件或 OBJ 文件,也可以是一个执行的动作名称,比如 clean。
  • 依赖(dependings):是用来产生目标的材料,比如源文件。一个目标经常会有多个依赖。
  • 命令(command):是生成目标时执行的动作。一个规则可以包含多条命令,每条命令占一行。

需要注意:每个命令行前面必须是一个 Tab 字符,也就是说命令行第一个字符必须是 Tab。这是最容易出错的地方之一。

通常,如果一个依赖发生了变化,就需要规则调用命令来更新或创建目标。不过并非所有目标都有依赖,比如 clean 这个目标的作用是清除文件,它就没有依赖。

规则一般用来解释怎样以及何时重建目标。make 会先调用命令处理依赖,进而才能创建或更新目标。当然,规则也可以用于解释怎样以及何时执行一个动作,比如打印提示信息。

一个 Makefile 文件可以包含规则以外的其他文本,但一个简单的 Makefile 文件只需要包含规则即可。虽然真实的规则比这个例子复杂,但格式完全一样。

02 Makefile 文件里的赋值方法

变量的定义语法形式如下:

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate
define immediate
deferred
endif

在 GNU make 中,对变量的赋值有两种方式:延时变量和立即变量。

区别在于定义方式和扩展时机不同。延时变量在使用时才展开,也就是真正使用时这个变量的值才确定;立即变量在定义时值就已经确定了。

使用 =、?= 定义,或者使用 define 指令定义的变量是延时变量;使用 := 定义的变量是立即变量。

还要注意,?= 仅仅在变量还没有定义的情况下有效,也就是说 ?= 被用来定义第一次出现的延时变量。

03 Makefile 常用函数

函数调用的格式如下:

$(function arguments)

其中 function 是函数名,arguments 是函数的参数。参数和函数名之间用空格或 Tab 隔开。如果有多个参数,它们之间用逗号隔开。这些空格和逗号并不是参数值的一部分。

内核的 Makefile 中会用到大量函数,下面介绍一些常用函数。

3.1 字符串替换和分析函数

3.1.1 $(subst from,to,text)

在文本 text 中使用 to 替换每一处 from。

例如:

$(subst ee,EE,feet on the street)

结果为:

fEEt on the strEEt

3.1.2 $(patsubst pattern,replacement,text)

寻找 text 中符合格式 pattern 的字,用 replacement 替换它们。pattern 和 replacement 中可以使用通配符。

例如:

$(patsubst %.c,%.o,x.c.c bar.c)

结果为:

x.c.o bar.o

3.1.3 $(strip string)

去掉字符串前导和结尾的空格,并将中间的多个空格压缩为单个空格。

例如:

$(strip a b c )

结果为:

a b c

3.1.4 $(findstring find,in)

在字符串 in 中搜寻 find。如果找到,返回值就是 find,否则返回空字符串。

例如:

$(findstring a,a b c)
$(findstring a,b c)

结果分别为 a 和空字符串。

3.1.5 $(filter pattern...,text)

返回 text 中由空格隔开且匹配格式 pattern... 的字,去除不符合格式的字。

例如:

$(filter %.c %.s,foo.c bar.c baz.s ugh.h)

结果为:

foo.c bar.c baz.s

3.1.6 $(filter-out pattern...,text)

返回 text 中由空格隔开且不匹配格式 pattern... 的字,去除符合格式的字。它是函数 filter 的反函数。

例如:

$(filter-out %.c %.s,foo.c bar.c baz.s ugh.h)

结果为:

ugh.h

3.1.7 $(sort list)

将 list 中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字列表。

例如:

$(sort foo bar lose)

返回值为:

bar foo lose

3.2 文件名函数

3.2.1 $(dir names...)

抽取 names... 中每一个文件名的路径部分。路径部分包括从文件名的首字符到最后一个斜杠(含斜杠)之前的所有字符。

例如:

$(dir src/foo.c hacks)

结果为:

src/ ./

3.2.2 $(notdir names...)

抽取 names... 中每一个文件名中除路径部分外的一切字符,也就是真正的文件名。

例如:

$(notdir src/foo.c hacks)

结果为:

foo.c hacks

3.2.3 $(suffix names...)

抽取 names... 中每一个文件名的后缀。

例如:

$(suffix src/foo.c src-1.0/bar.c hacks)

结果为:

.c .c

3.2.4 $(basename names...)

抽取 names... 中每一个文件名中除后缀外的一切字符。

例如:

$(basename src/foo.c src-1.0/bar hacks)

结果为:

src/foo src-1.0/bar hacks

3.2.5 $(addsuffix suffix,names...)

参数 names... 是一系列文件名,文件名之间用空格隔开;suffix 是一个后缀名。函数会把 suffix 的值附加在每一个独立文件名的后面,完成后将文件名串联起来,文件名之间用单个空格隔开。

例如:

$(addsuffix .c,foo bar)

结果为:

foo.c bar.c

3.2.6 $(addprefix prefix,names...)

参数 names... 是一系列文件名,文件名之间用空格隔开;prefix 是一个前缀名。函数会把 prefix 的值附加在每一个独立文件名的前面,完成后将文件名串联起来,文件名之间用单个空格隔开。

例如:

$(addprefix src/,foo bar)

结果为:

src/foo src/bar

3.2.7 $(wildcard pattern)

参数 pattern 是一个文件名格式,可以包含通配符,通配符用法和 shell 中的类似。函数 wildcard 的结果是一列与格式匹配且真实存在的文件名称,文件名之间用一个空格隔开。

例如,假设当前目录下有文件 1.c、2.c、1.h、2.h,则:

c_src := $(wildcard *.c)

结果为:

1.c 2.c

3.3 其他函数

3.3.1 $(foreach var,list,text)

前两个参数 var 和 list 会首先扩展,而最后一个参数 text 此时不扩展。接着,list 扩展后得到的每个字都会赋给变量 var。然后 text 引用该变量进行扩展,因此 text 每次扩展的结果都不相同。

函数的结果是由空格隔开的 text 在 list 中多次扩展后得到的新列表。也就是说,text 多次扩展后的字会串联起来,字与字之间由空格隔开。

下面是一个简单例子,把变量 files 的值设置为 dirs 中所有目录下的所有文件列表:

dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

3.3.2 $(if condition,then-part[,else-part])

首先把第一个参数 condition 的前导空格和结尾空格去掉,然后扩展。如果扩展结果为非空字符串,则条件 condition 为“真”;如果扩展结果为空字符串,则条件 condition 为“假”。

如果条件为“真”,就计算第二个参数 then-part 的值,并将该值作为整个函数 if 的值。

如果条件为“假”,并且第三个参数存在,就计算第三个参数 else-part 的值,并将该值作为整个函数 if 的值;如果第三个参数不存在,函数 if 将什么也不计算,返回空值。

需要注意:只能计算 then-part 和 else-part 二者之一,不能同时计算。这样做有可能产生副作用,比如调用 shell 函数时。

3.3.3 $(origin variable)

变量 variable 是一个查询变量的名称,不是对该变量的引用。所以这里不能采用 $ 和圆括号的格式书写该变量。当然,如果需要使用非常量的文件名,可以在文件名中使用变量引用。

函数 origin 的结果是一个字符串,用来表示该变量是如何定义的:

  • undefined:变量从未定义;
  • default:变量是缺省定义;
  • environment:变量作为环境变量定义,选项 -e 没有打开;
  • environment override:变量作为环境变量定义,选项 -e 已打开;
  • file:变量在 Makefile 中定义;
  • command line:变量在命令行中定义;
  • override:变量在 Makefile 中用 override 指令定义;
  • automatic:变量是自动变量。

3.3.4 $(shell command arguments)

函数 shell 是 make 与外部环境进行通讯的工具。函数 shell 的执行结果和在控制台上执行 command arguments 的结果相似。不过如果 command arguments 的结果中含有换行符或回车符,它们在函数 shell 的返回结果中会被处理成单个空格;如果返回结果末尾是换行符或回车符,则会被去掉。

例如,假设当前目录下有文件 1.c、2.c、1.h、2.h,则:

c_src := $(shell ls *.c)

结果为:

1.c 2.c

04 写在最后

这一节的内容不要求一次记住全部函数用法。你可以在阅读内核、bootloader、应用程序里的 Makefile 时,把它当作手册随时查询。掌握基本规则和几个高频函数之后,大部分日常构建需求都能应对。




上一篇:Agent Harness 要不要自建?LangChain 创始人的选型与评估建议
下一篇:嵌入式通用Makefile模板:仿Linux内核的多目录递归编译与配置方法
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-28 02:40 , Processed in 0.109311 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表