嵌入式Linux开发之Makefile
makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile的好处就是:
- “自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- 节约编译时间(没改动的文件不编译)。
make是一个命令工具,是一个解释makefile中指令的命令工具。
由于u-boot的Makefile中存在相互调用,这里介绍一下make -f和make -C区别:
- -C选项 :Makefile中使用make -C会改变当前的工作目录,表示到子目录下执行子目录的Makefile,顶层Makefile中的export的变量还有make默认的变量是可以传递给子目录中的Makefile的;
- -f选项:顶层Makefile使用make -f调用子目录中的文件(文件名可以随意,不一定用Makefile作为文件名,顶层Makefile中的export的变量也可以传递变量到底层目录,另外在命令行中加入变量赋值选项,将覆盖顶层Makefile中export的变量;
注意:在顶层Makefile中使用-f选项,例如make -f ./xxx/xx/build.mk 此时make命令的工作目录仍然是顶层目录,即CUDIR变量依然是./目录而不是./xxx/xx/目录。
一、Makefile规则
1.1 基本规则(注释规范)
一个简单的Makefile 主要的 5个部分 (显示规则, 隐晦规则, 变量定义, 文件指示, 注释),其样式如下:
target:prerequisites
command
...
...
=====================
目标 : 依赖文件
[tab键] 命令
...
...
这是一个文件依赖关系,也就是说target(一个或多个目标文件)依赖于prerequisites中的文件,其生成规则定义在command中,而且只要prerequisites中有一个以上的文件比target文件更新的话,command所定义的命令就会被执行,这是makefile的最基本规则,也是makefile中最核心的内容。
其中:
- target: 是目标文件,可以是object file,也可以是执行文件,还可以是一个标签label。如果目标文件的更新时间晚于依赖文件的更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则重新编译并更新目标。
- prerequisites:即目标文件由哪些文件生成。如果依赖条件中存在不存在的依赖条件,则会寻找其它规则是否可以产生依赖条件。例如:规则一是生成目标 hello.elf需要使用到依赖条件 hello.o,但是 hello.o 不存在。则 Makefile 会寻找到一个生成 hello.o 的规则二并执行。
- command:即通过执行该命令,由依赖文件生成目标文件,规则中的命令被传递给shell进行解析执行。注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。如果同一行有多条命令,命令间用分号分割。。
(1) 显示规则 : 说明如何生成一个或多个目标文件(包括 生成的文件, 文件的依赖文件, 生成的命令);
(2) 隐晦规则 : make的自动推导功能所执行的规则;
(3) 变量定义: Makefile中定义的变量;
(4) 文件指示 : Makefile中引用其他Makefile; 指定Makefile中有效部分; 定义一个多行命令;
(5) 注释: Makefile只有行注释 "#", 如果要使用或者输出"#"字符, 需要进行转义, "\#";
GNU make 的工作方式:
- 读入主Makefile (主Makefile中可以引用其他Makefile);
- 读入被include的其他Makefile;
- 初始化文件中的变量;
- 推导隐晦规则, 并分析所有规则;
- 为所有的目标文件创建依赖关系链;
- 根据依赖关系, 决定哪些目标要重新生成;
- 执行生成命令;
Makefile文件注释有以下几种方式:
- 单行注释:makefile 把 # 字符后面的内容作为注释内容处理(shell、perl 脚本也是使用 # 字符作为注释符)
-
多行注释:如果需要注释多行,在注释行的结尾加行反斜线(\),下一行也被注释,可以注释多行。
1.2 规则中的通配符
- * : 表示任意一个或多个字符;
- ? : 表示任意一个字符;
- [...]: ex. [abcd] 表示a,b,c,d中任意一个字符, [^abcd]表示除a,b,c,d以外的字符, [0-9]表示 0~9中任意一个数字;
- ~ :表示用户的home目录;
- %:通配符,如%.o:%c,表示表示把所有的.c文件编译输出成.o文件。
1.3 路径搜索
当一个Makefile中涉及到大量源文件时(这些源文件和Makefile极有可能不在同一个目录中),这时, 最好将源文件的路径明确在Makefile中, 便于编译时查找。Makefile中有个特殊的变量VPATH就是完成这个功能的。指定了 VPATH 之后, 如果当前目录中没有找到相应文件或依赖的文件, Makefile会到VPATH 指定的路径中再去查找。
VPATH 使用方法:
- vpath <directories>: 当前目录中找不到文件时, 就从<directories>中搜索;
- vpath <pattern> <directories>:符合<pattern>格式的文件, 就从<directories>中搜索;
- vpath <pattern>:清除符合<pattern>格式的文件搜索路径;
- vpath:清除所有已经设置好的文件路径;
# 示例1 - 当前目录中找不到文件时, 按顺序从 src目录 ../parent-dir目录中查找文件
VPATH src:../parent-dir
# 示例2 - .h结尾的文件都从 ./header 目录中查找
VPATH %.h ./header
# 示例3 - 清除示例2中设置的规则
VPATH %.h
# 示例4 - 清除所有VPATH的设置
VPATH
1.4 示例
我们以hello.c文件为例:
#include <stdio.h>
int main(int argc,char *argv[])
{
printf("Hello World!\n");
return 0;
}
然后编写Makefile(这里使用的是gcc、而不是arm-linux-gcc):
ALL: hello.o
hello.o: hello.c
gcc hello.c -o hello.o
命令格式:
- make (目标,省略的话默认执行第一个目标):
编译并执行:
make
./hello.o
把hello.c文件复制一份,重命名为hello_copy.c。修改Makefile的内容如下:
ALL: $(obj) $(obj):%.o: %.c gcc $< -o $@ .PHONY: clean clean: rm -f $(obj)
- 第一个target是ALL,而ALL由两个.o文件构成,对应的两个.c文件可以同时编译;
- target可以是变量,例子中的obj是一个变量;
- 这里加上了一个.PHONY、@,究竟是啥呢?这个后面我们会介绍。
二、Makefile函数
Makefile 中自带了一些函数, 利用这些函数可以简化 Makefile 的编写。
函数调用语法如下:
$(<function> <arguments>)
# 或者
${<function> <arguments>}
- <function> 是函数名;
- <arguments> 是函数参数;
2.1 获取匹配模式文件名函数wildcard
- 语法:$(wildcard PATTERN)
- 函数功能:列出当前目录下所有符合模式“ PATTERN”格式的文件名。“PATTERN”使用 shell可识别的通配符,包括“ ?”(单字符)、“*”(多字符)等。
- 返回值:空格分割的、存在当前目录下的所有符合模式“ PATTERN”的文件名。
例如:
SRC = $(wildcard ./*.c)
匹配目录下所有的 .c 文件,并将其赋值给 SRC 变量。
2.2 模式替换函数patsubst
pat 是 pattern 的缩写,subst 是 substring 的缩写。
- 语法:$(patsubst PATTERN,REPLACEMENT,TEXT)
- 函数功能:搜索“ TEXT”中以空格分开的单词,将否符合模式“ TATTERN ”替换为“REPLACEMENT ”。参数“PATTERN”中可以使用模式通配符 “%”来代表一个单词中的若干字符。 如果参数“REPLACEMENT ”中也包含一个“%”,那么“ REPLACEMENT ”中的“ %”将是“ TATTERN”中的那个“ %”所代表的字符串。在“ TATTERN ”和“REPLACEMENT ”中,只有第一个“ %”被作为模式字符来处理,之后出现的不再作模式字符(作为一个字符)。在参数中如果需要将第一个出现的“ %”作为字符本身而不作为模式字符时,可使用反斜杠“ ”进行转义处理(转义处理的机制和使用静态模式的转义一致,
- 返回值:替换后的新字符串。
参数 “TEXT ”单词之间的多个空格在处理时被合并为一个空格,并忽略前导和结尾空格。
例如:
OBJ = $(patsubst %.c, %.o, $(SRC))
这个函数有三个参数,意思是取出 SRC 中所有的值,然后将 “.c” 替换为 “.o”,最后赋值给 OBJ 变量。
示例:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc $(OBJ) -o hello.out
$(OBJ): $(SRC)
gcc -c $(SRC) -o $(OBJ)
这里我们先将所有的 “.c” 文件编译为 “.o” 文件,这样后面更改某个 “.c” 文件时,其它的 “.c” 文件将不再编译,而只是编译有更改的 “.c” 文件,可以大大节约大项目中的编译速度。
需要注意的是:
- .o文件一般是通过编译的但还未链接的。
- .out文件一般都是经过相应的链接产生的可执行文件(linux下)。
2.3 foreach 函数
函数“foreach ”不同于其它函数。它是一个循环函数。类似于 Linux 的 shell 中的for 语句:
- 语法:$(foreach VAR,LIST,TEXT)
- 函数功能: 这个函数的工作过程是这样的:如果需要(存在变量或者函数的引用),首先展开变量“ VAR”和“ LIST”的引用;而表达式“ TEXT”中的变量引用不展开。执行时把“ LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“ TEXT”表达式。重复直到“ LIST”的最后一个单词(为空时结束)。“TEXT ”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“ VAR”的引用,那么“ VAR”的值在每一次展开式将会到的不同的值。
- 返回值: 空格分割的多次表达式“ TEXT ”的计算的结果。
2.4 过滤函数 filter
1).找出符合PATTERN 格式的值
- 语法:$(filter PATTERN... ,TEXT)
- 函数功能:过滤掉字串“ TEXT”中所有不符合模式“ PATTERN ”的单词,保留所有符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
- 返回值:空格分割的“ TEXT”字串中所有符合模式“ PATTERN ”的字串。
示例:
# Makefile 内容
all:
@echo $(filter %.o %.a,program.c program.o program.a)
执行命令:
# bash 中执行 make $ make program.o program.a
2).找出不符合PATTERN 格式的值
- 语法:$(filter-out PATTERN... ,TEXT)
- 函数功能:过滤掉字串“ TEXT”中所有符合模式“ PATTERN ”的单词,保留所有不符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
- 返回值:空格分割的“ TEXT”字串中所有不符合模式“ PATTERN ”的字串。
示例:
# Makefile 内容
all:
@echo $(filter-out %.o %.a,program.c program.o program.a)
执行命令:
# bash 中执行 make $ make program.c
2.5 if函数
这里的if是个函数,它用于判断条件是否存在或者为true。 和后面将会介绍的条件判断不一样, 后面介绍的条件判断属于Makefile的关键字。
- 语法:$(if <condition>,<then-part>) 或 $(if <condition>,<then-part>,<else-part>)
- 函数功能:用于条件判断。
- 返回值:返回符合条件的部分的值。
示例:
# Makefile 内容
val := a
objects := $(if $(val),$(val).o,nothing)
no-objects := $(if $(no-val),$(val).o,nothing)
all:
@echo $(objects)
@echo $(no-objects)
执行命令:
# bash 中执行 make
$ make
a.o
nothing
2.6 创建新的参数化函数call
- 语法:$(call <expression>,<parm1>,<parm2>,<parm3>...)
- 函数功能:函数调用。
- 返回值:返回调用的函数的执行结果。
示例:
# Makefile 内容
log = "====debug====" $(1) "====end===="
all:
@echo $(call log,"正在 Make")
执行命令:
# bash 中执行 make
$ make
====debug==== 正在 Make ====end====
2.7 判断变量的来源函数orgin
- 语法:$(origin <variable>)
- 函数功能:判断变量的来源,返回值有以下类型:
类型 |
含义 |
undefined | <variable> 没有定义过 |
default | <variable> 是个默认的定义, 比如 CC 变量 |
environment | <variable> 是个环境变量, 并且 make时没有使用 -e 参数 |
file | <variable> 定义在Makefile中 |
command line | <variable> 定义在命令行中 |
override | <variable> 被 override 重新定义过 |
automatic | <variable> 是自动化变量 |
示例:
# Makefile 内容
val-in-file := test-file
override val-override := test-override
all:
@echo $(origin not-define) # not-define 没有定义
@echo $(origin CC) # CC 是Makefile默认定义的变量
@echo $(origin PATH) # PATH 是 bash 环境变量
@echo $(origin val-in-file) # 此Makefile中定义的变量
@echo $(origin val-in-cmd) # 这个变量会加在 make 的参数中
@echo $(origin val-override) # 此Makefile中定义的override变量
@echo $(origin @) # 自动变量, 具体前面的介绍
执行命令:
# bash 中执行 make
$ make val-in-cmd=val-cmd
undefined
default
environment
file
command line
override
automatic
2.8 shell
- 语法:$(shell <shell command>)
- 函数功能:执行一个shell命令,作用和 `<shell command>` 一样, ` 是反引号。
- 返回值:将shell命令的结果作为函数的返回。
2.9 去空格函数strip
- 语法:$(strip <string>)
- 函数功能:去掉 <string> 字符串中开头和结尾的空字符。
- 返回值:被去掉空格的字符串值。
示例:
# Makefile 内容 VAL := " aa bb cc " all: @echo "去除空格前: " $(VAL) @echo "去除空格后: " $(strip $(VAL))
执行命令:
# bash 中执行 make $ make 去除空格前: aa bb cc 去除空格后: aa bb cc
2.10 查找字符串函数findstring
- 语法 $(findstring <find>,<in>)、
- 函数功能: 在字符串 <in> 中查找 <find> 字符串。
- 返回值: 如果找到, 返回 <find> 字符串, 否则返回空字符串。
示例:
# Makefile 内容 VAL := " aa bb cc " all: @echo $(findstring aa,$(VAL)) @echo $(findstring ab,$(VAL))
执行命令:
# bash 中执行 make $ make aa
2.11 更多函数
由于Makefile函数过多,就不一一介绍了,更多函数信息请移步此处。
三、makefile内置变量
3.1 shell变量(CXX、CC等)
下面只列出一些C相关的:
变量名 |
含义 |
RM | rm -f |
AR | ar |
CC | cc |
CXX | g++ |
示例:
# Makefile 内容
all:
@echo $(RM)
@echo $(AR)
@echo $(CC)
@echo $(CXX)
# bash 中执行make, 显示各个变量的值
$ make
rm -f
ar
cc
g++
3.2 自动变量($@、%<等)
Makefile 中很多时候通过自动变量来简化书写, 各个自动变量的含义如下:
自动变量 |
含义 |
$@ | 目标集合 |
$% | 当目标是函数库文件时, 表示其中的目标文件名 |
$< | 第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标 |
$? | 比目标新的依赖目标的集合 |
$^ | 所有依赖目标的集合, 会去除重复的依赖目标 |
$+ | 所有依赖目标的集合, 不会去除重复的依赖目标 |
$* | 这个是GNU make特有的, 其它的make不一定支持 |
示例:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc -o $@ $<
$(OBJ): $(SRC)
gcc -c -o $@ $<
四、其它常用功能
4.1 代码清理clean
我们可以编译一条属于自己的 clean 语句,来清理 make 命令所产生的所有文件。例如
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc -o $@ $<
$(OBJ): $(SRC)
gcc -c -o $@ $<
clean:
rm -rf $(OBJ) *.out
这样我们就可以使用make clean 命令来清理生成的文件了:
提示:make命令是可以带上目标名的,如果make后面不跟目标名字的话,默认生成第一个目标,当带上目标名的话,生成指定的目标。
4.2 伪目标 .PHONY
上面我们写了一个 clean 语句,使得我们执行 “make clean” 命令的时候,可以清理我们生成的文件。
但是假如还存在一个文件名就是 clean 文件,那么我们再执行 “make clean” 命令的时候就只是显示:
make clean
make: `clean' is up to date.
为什么?我们看一看Makefile的核心规则:
- 目标文件不存在;
- 某个依赖文件比目标文件新;
但是现在目录中有名为clean的文件,那目标文件存在,那就取决于依赖,但是在Makefile中clean目标没有依赖,所以没有办法通过判断依赖的的时间去更新clean目标,所以clean目标文件一直都是目录中那个clean文件。所以说,如果目录中有和clean同名文件时就没有办法执行clean操作了。
解决方法就是我们使用伪目标,把这个目标定义为假想目标这样就可以避免出现上面的问题了,例如:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc -o $@ $<
$(OBJ): $(SRC)
gcc -c -o $@ $<
clean:
-rm -rf $(OBJ) hello.out
.PHONY: clean ALL
通常,我们也会把 ALL 也设置为伪目标。
4.3 Makefile命令前缀
我们知道在Makefile文件中是可以编写shell命令的,Makefile 中书写shell命令时可以加2种前缀 @ 和 -, 或者不用前缀。
3种格式的shell命令区别如下:
- 不用前缀:输出执行的命令以及命令执行的结果, 出错的话停止执行;
- 前缀 @ :关闭命令回显,只输出命令执行的结果, 出错的话停止执行;
- 前缀 - :命令执行有错的话, 忽略错误, 继续执行;
不用前缀示例:
# Makefile 内容 (不用前缀)
all:
echo "没有前缀"
cat this_file_not_exist
echo "错误之后的命令" <-- 这条命令不会被执行
执行命令:
# bash中执行 make
$ make
echo "没有前缀" <-- 命令本身显示出来
没有前缀 <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1
前缀 @ 示例:
# Makefile 内容 (前缀 @)
all:
@echo "没有前缀"
@cat this_file_not_exist
@echo "错误之后的命令" <-- 这条命令不会被执行
执行命令:
# bash中执行 make
$ make
没有前缀 <-- 只有命令执行的结果, 不显示命令本身
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1
前缀 - 示例:
# Makefile 内容 (前缀 -)
all:
-echo "没有前缀"
-cat this_file_not_exist
-echo "错误之后的命令" <-- 这条命令会被执行
执行命令:
# bash中执行 make
$ make
echo "没有前缀" <-- 命令本身显示出来
没有前缀 <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: [all] Error 1 (ignored)
echo "错误之后的命令" <-- 出错之后的命令也会显示
错误之后的命令 <-- 出错之后的命令也会执行
4.4 引用其它的Makefile
语法: include <filename> (filename 可以包含通配符和路径)。
示例:
# Makefile 内容
all:
@echo "主 Makefile begin"
@make other-all
@echo "主 Makefile end"
include ./other/Makefile
# ./other/Makefile 内容
other-all:
@echo "other makefile begin"
@echo "other makefile end"
执行命令:
# bash中执行 make
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/makefile/other'
other makefile begin
other makefile end
make[1]: Leaving directory `/path/to/makefile/other'
主 Makefile end
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2018-05-30 第十九节,去噪自编码和栈式自编码