makefie中的几种用法
1、自定义变量
1 2 3 4 5 6 | A = apple B = I love China C = $(A) tree all: @ echo $C |
echo前面的@代表命令本身不打印处理出来,如果不加@会输出以下结果。
all为目标,当makefile文件中有两个目标时,执行make命令会默认执行第一个目标,也可以使用make +目标名执行相应的目标。
2、makefile中的所有.o文件用一个变量OBJ来代表
3、系统预定义变量
CFLAGS、CC、MAKE、Shell等等,这些变量已经有了系统预定义好的值,当然我们可以根据需要重新给他们赋值,例如CC的默认值是gcc,当我们需要使用c编译器的时候可以直接使用他:
1 2 3 4 5 6 7 | OBJ = a.o b.o image:$(OBJ) $(CC)$(OBJ) -o image a.o:a.c $(CC) a.c -o a.o -c b.o:b.c $(CC) b.c -o b.o -c |
这样做的好处是:在不同平台中,c编译器的名称也许会发生变化,如果我们的Makefile使用了100处c编译器的名字,那么换一个平台我们只需要重新给预定义变量CC赋值次即可,而不需要修改100处不同的地方。比如我们换到ARM开发平台中,只需要重新给CC赋值为arm-Linux-anu-qcc,请看:
1 2 3 4 5 6 7 8 | OBJ = a.o b.o x.o y.o CC = arm-Linux-gnu- gcc image:$(OBJ) $(CC)$(OBJ) -o image a.o:a.c $(CC) a.c -o a.o -c b.o:b.c $(CC) b.c -o b.o -c |
4、变量递归定义
1 2 | A = I love $(B) B = China |
此处,在变量B出现之前,变量A的定义包含了对变量B的引用,由于A的定义方式是所谓的“递归”定义方式,因此当出现$(B)时会对全文件进行搜索,找到B的值并代进中,结果变量A的值是“I love China”
递归定义的变量有两个缺点:
- 可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使make执行失败。例如A=$(A),这将导致无限嵌套迭代。
- 这种风格变量的定义中如果使引用了某一个函数,那么函数总会在其被引用的地方被执行。是因为这种风格变量的定义中,对函数引用的替换展开发生在展开它自身的时候,而不是在定义它的时候。这样所带来的问题是,可能可能会使make的执行效率降低,同时对某些变量和函数的引用出现问题。特别是当变量定义中引用了"Shell"和"wildcard"函数的情况,可能出现不可控制或者难以预料的错误,因为我们无法确定它在何时会被展开。
5、直接定义
1 2 | B= China A:= I love $(B) |
此处,定义A时用的是所谓的“直接”定义方式,说白了就是如果其定义里出现有对其他变量的引用的话,只会其前面的语句进行搜寻(不包含自己所在的那一行),而不是搜寻整个文件,因此,如果此处将变量A和变量B的定义交换一个位置:
1 2 | A:= I love $(B)B= China 则A的值将不包含China,因此在定义A时B的值为空。 |
6、条件定义方式
有时我们需要先判断一个变量是否已经定义了,如果已经定义了则不作操作,如果没有定义再来定义它的值,这时最方便的方法就是采用所谓的条件定义方式:
1 2 | A= apple A?= I love China |
此处对A进行了两次定义,其中第二次是条件定义,其含义是:如果A在此之前没有定义,则定义为“I love China”,否则维持原有的值。
7、追加变量的值
1 2 | A = apple A += tree |
这样,变量A的值就是apple tree.
8、修改变量的值
1 2 | A = srt.c string.c tcl.c B = $(A:%.c=%.o) |
这样,变量B的值就变成了srt.o string.o tcl.o。例子中$(A:%.c=%.o)的意思是:将变量A中所有以.c作为后缀的单词,替换为以.o作为后缀。其实这种变量的替换功能是内嵌函数patsubst的简单版本,使用patsubst也可以实现这个替换的功能:
1 2 | A= srt.c string.c tcl.c B = $(patsubst %.c, %.o,$(A)) |
9、override一个变量
1 | override CFLAGS += -Wall |
可见,虽然Makefile定义了A的值为"an apple tree",但被命令行定义的A的值覆盖了,变成了"an elephant”。如果不想被覆盖,则可以写成:
1 2 3 | override A = an apple tree all: echo $(A) |
10、VPATH
这个特殊的变量用以指定Makefile中文件的备用搜寻路径:当Makefile中的目标文件或者依赖文件不在当前路径时,make会在此变量所指定的目录中搜寻,如果VPATH包含多个备用路径,他们使用空格或者冒号隔开。例如:
1 2 3 4 5 6 7 8 9 10 11 | vincent@ubuntu:~$ tree|——Makefile |—src1/ l—a.c |— src2/ l—b.c vincent@ubuntu: ~$ cat Makefile -n VPATH = src1/:src2/ 指定文件搜寻除当前路径之外的备用路径 all: a b a:a.c 若 make 发现当前路径下不存在a.c,则会到VPATH中去找 gcc $^ -o $@ b:b.c gcc $^ -o $@ |
1 2 3 4 5 6 | vpath %.c = src1/:src2/ 指定本Makefile中.c文件的可能路径 vpath %.h = include/ 指定本Makefile中.h文件的可能路径 all:a b a:a.c head .h $(CC)$< -o $@b:b.c head .h $(CC)$< -o $@ |
11、make
当需要在一个Makefile中调用子Makefile时,用到的变量就是MAKE,实际上该变量代表了当前系统中make软件的全路径,比如:/usr/bin/make。其具体用法是:
1 | $(MAKE) -C subdir/ |
其中-C subdir/代表指定子Makefile所在目录,详细案例请参照前面“导出变量”小节。
12、MAKEFLAGS
此变量代表了在执行make时的命令行参数,这个变量是缺省会被传递给子Makefile的特殊变量之一。比如:
1 2 | all: echo $(MAKEFLAGS) |
13、隐式规则
1 2 3 | OBJ = a.o b.o x.o y.o image:$(OBJ) $(CC)$(OBJ) -o image |
可以看到,虽然后四个规则的目标、依赖文件和编译语句都没写,但是执行make 也照样可以运行,可见make 会自动帮我们找到.o文件所需要的源程序文件,也能自动帮我们生成对应的编译语句,这个情况称之为Makefile 的隐式规则。
1 2 3 4 5 6 | OBJ= a.o b.o x.o y.o image:$(OB) $(CC) $(OBJ) -o image clean: $(RM)$(OBJ) image .PHONY: clean |
clean目标是用来清理生成的目标文件以及image的规则,执行make clean就可以清理。如果当前目录下有个clean.c的文件,那么会导致makefile自动生成其对应的编译语句,从而引起混淆,.PHONY用来告诉makefile不要对clean运用隐式规则,事实上,不能运用隐式规则的目标被称为伪目标。
14、静态规则
1 OBJ = a.o b.o x.o y.o
2 image:$(OBJ)
3 $(CC)$(OBJ) -o image
4 $(OBJ):%.o:%.c
5 $(CC) $^ -o $@-wall -c
6 clean:
$(RM)$(OBJ) image
7 .PHONY: clean
第6、7行运用了所谓的静态规则,其工作原理是:$(OB])被称为原始列表,即(a.ob.o x.o y.o),紧跟在其后的%.o被称为匹配模式,含义是在原始列表中按照这种指定的模式挑选出能匹配得上的单词(在本例中要找出原始列表里所有以.o为后缀的文件)作为规则的目标,这个过程用下图演示:
15、多目标规则
一般我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c文件列表,
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
subst是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替
换操作作用的字串。 $(subst .o,.c,$@)代表把所有目标文件的.o后缀替换成.c,然后结果为所有的.c文件
1 2 | a.o:a.c b.c $(CC)$(subst .o,.c,$@) -o $-c |
即:
1 2 | a.o:a.c b.c $(CC) a.c -o a.o -c |
16、条件判断
17、makefie中字符串处理函数
(1)SRC = $(wildcard *c)
1 | 等效于 SRC = a.c b.c |
(2)$(subst FROM,TO,TEXT)
功能:
1 | 将字符串TEXT中的字符FROM替换为TO。 |
1 | 返回: |
1 | 替换之后的新字符串。 |
(3)$(strip STRING)
功能:
1 | 去掉字符串中开头和结尾的多余的空白符(掐头去尾),并将其中连续的多个空白符合并为一个。注意:所谓的空白符指的是空格、制表符。 |
返回:
1 | 去掉多余空白符之后的新字符串。 |
范例:
1 | $(strip apple tree )处理之后,变量A的值是 "apple tree" . |
(4)$(findstring FIND,STRING)
1 | 功能: |
1 | 在给定的字符串 STRING中查找FIND子串。 |
1 | 返回: |
1 | 找到则返回FIND,否额返回空。 |
1 | 范例: |
1 2 | A= $(findstring pp, apple tree) B = $(findstring xx, apple tree)变量A的值是 "pp" ,变量B的值是空。 |
(5),$(filter-out PATTERN,TEXT)
功能:
1 | 过滤掉TEXT中所有符合给定模式PATTERN 的单词,与函数 filter 功能相反。 |
返回:
1 | TEXT中所有不符合模式组合PATTERN的单词组成的子串。 |
范例:
1 | A= a.c b.o c.s d.txt B= $(filter %.c %.o,$(A))过滤后变量B的值是 "c.s d.txt" 。 |
(6)$(sort LIST)
功能:
1 | 将字符串LIST中的单词按字母升序的顺序排序,并且去掉重复的单词。 |
返回:
1 | 排完序且没有重复单词的新字符串。 |
范例:
1 2 | A= foo bar lose foo ugh B= $( sort $(A)) 处理后变量B的值是 "bar foo lose ugh" 。 |
(7)$(word N,TEXT)
功能:
1 | 取字符串 TEXT中的第N个单词。注意,N必须为正整数。 |
返回:
1 | 第Ⅳ个单词(如果N大于TEXT中单词的总数则返回空)。 |
范例:
1 2 | A= an apple tree B =$(word 2, $(A)) 处理后变量B的值是 "apple" . |
(8)$(wordlist START,END,TEXT)
功能:
1 | 取字符串TEXT中介于 START和END之间的子串。 |
返回:
1 | 介于 START和 END之间的子串(如果 START大于TEXT中单词的总数或者START大于END时返回空,否则如果END大于TEXT中单词的总数则返回从START开始到TEXT的最后一个单词的子串)。 |
范例:
1 2 | A= the apple tree is over 5 meters tall B= $(wordlist 4,100,$(A)) 处理后变量B的值是 "is over 5 meters tall" 。 |
(9)$(words TEXT)
功能:
1 | 计算字符串 TEXT的单词数。 |
返回:
1 | 字符串 TEXT的单词数。 |
范例:
1 2 | A= the apple tree is over 5 meters tall B= $(words $(A)) 处理后变量B的值是"8”。 |
(10)$(firstword TEXT)
功能:
1 | 取字符串 TEXT中的第一个单词。相当于$(word 1 TEXT) |
返回:
1 | 字符串 TEXT的第一个单词。 |
范例:
1 2 | A= the apple tree is over 5 meters tall B= $(firstword $(A)) 处理后变量B的值是 "the" 。 |
以上10个函数是make内嵌的的文本处理函数。在书写Makefile时可搭配使用,来实现复杂功能。
18、makefile中文件名处理函数
(1)$(dir NAMES)
功能:
1 | 取文件列表NAMES中每一个路径的目录部分。 |
返回:
1 | 每一个路径的目录部分组成的新的字符串。 |
范例:
1 | A= /etc/init .d /home/vincent/ .bashrc /usr/bin/manB = $( dir $(A))处理后变量B的值是" /etc/ /home/vincent/ /usr/bin/ ”。 |
(2)$(notdir NAMES)
功能:
1 | 取文件列表NAMES中每一个路径的文件名部分。 |
返回:
1 | 每一个路径的文件名部分组成的新的字符串。注意:如果NAMES中存在不包含斜线的文件名,则不改变这个文件名,而以反斜线结尾的文件名,用空串代替。 |
范例:
1 2 | A = /etc/init .d /home/vincent/ .bashrc /usr/bin/manB = $( dir $(A)) 处理后变量B的值是 "init.d .bashrc man" 。 |
(3)$(suffix NAMES)
功能:
1 | 取文件列表NAMES中每一个路径的文件的后缀部分。后缀指的是最后一个.后面的子串。 |
返回:
1 | 每一个路径的文件名的后缀部分组成的新的字符串。 |
范例:
1 2 | A= /etc/init .d /home/vincent/ .bashrc /usr/bin/manB = $(suffix $(A)) 处理后变量B的值是 ".d .bashrc" 。 |
(4)$(basename NAMES)
功能:
1 | 取文件列表NAMES中每一个路径的文件的前缀部分。前缀指的是最后一个.后面除了后缀的子串。 |
返回:
1 | 每一个路径的文件名的前缀部分组成的新的字符串。 |
范例:
1 2 3 | A = /etc/init .d /home/vincent/ .bashrc /usr/bin/man B= $( basename $(A)) 处理后变量B的值是 "/etc/init /home/vincent/ /usr/bin/man" 。 |
(5)$(addsuffix SUFFIX,NAMES)
功能:
1 | 为文件列表NAMES中每一个路径的文件名添加后缀SUFFIX。 |
返回:
1 | 添加了后缀SUFFIX的字符串。 |
范例:
1 2 3 4 5 | A= /etc/init .d /home/vincent/ .bashrc /usr/bin/man B= $(addsuffix .bk,$(A)) 处理后B为 "/etc/init.d.bk /home/vincent/.bashrc.bk /usr/bin/man.bk" 。 |
(6)$(addprefix PREFIX,NAMES)
功能:
1 | 为文件列表NAMES中每一个路径的文件名添加前缀PREFIX。 |
返回:
1 | 添加了前缀PREFIX的字符串。 |
范例:
1 2 3 4 | A = /etc/init .d /home/vincent/ .bashrc /usr/bin/man B= $(addprefix host:,$(A)) 处理后B的值为: "host:/etc/init.d host:/home/vincent/.bashrc host:/usr/bin/man" 。 |
(7)$(wildcard PATTERN)
功能:
1 | 获取匹配模式为PATTERN的文件名。 |
返回:
1 | 匹配模式为PATTERN的文件名。 |
范例:
1 | A= $(wildcard*.c) |
(8)$(foreach VAR,LIST,TEXT)
功能:
1 2 | 首先展开变量 "VAR" 和 "LIST" ,而表达式 "TEXT" 中的变量引用不被展开。执行时把 "LIST" 中使用空格分割的单词依次取出赋值给变量 "VAR" ,然后执行 "TEXT" 表达式,重复直到 "LIST" 的最后一个单词(为空时结束)。 它是一个循环函数,类似于Linux的Shell中的循环。注意:由于 "TEXT" 中的变量或者函数引用在执行时才被展开,因此如果在 "TEXT" 中存在对 "VAR" 的引用,那么 "VAR" 的值在每一次展开式将会到的不同的值。 |
返回:
1 | 以空格分隔的多次表达式“TEXT”的计算的结果。 |
范例:
(9)$( if CONDITION,THEN-PART[,ELSE-PART])
功能:
1 | 判断CONDITION是否为空,如果非空则执行THEN-PART且将结果作为函数的返回值,否则如果为空则执行ELSE-PART且将结果作为函数的返回值,如果此时没有ELSE-PART则函数返回空。 |
返回:
1 | 根据CONDITION返回THEN-PART或者ELSE-PART的执行结果。 |
范例:
1 2 | install - dir := $( if $(INSTALL__DIR),$(INSTALL_DIR),extra) 先判断INSTALL_DIR是否为空,如果为空则将extra赋值给 install - dir ,否则如果不为空,则 install - dir 的值等于$(INSTALL_DIR)(该范例摘选自Linux-3.9.8源码顶层Makefile)。 |
(10)$(call VAR,ARGS,...)
功能:
1 | 执行VAR,并将ARGS一一对应地替换VAR里面的$(1)、$(2)……。因此函数$(call)被称为是Makefile中唯一一个创建定制参数的函数。 |
返回:
1 | 将ARGS替换VAR中的$(1)、$(2)……之后VAR的执行结果。 |
范例1:
1 2 | A = my name is $(1) $(2)B = $(call A, Michael,Jackson) 将Michael,Jackson分别替换变量A里面的$(1)和$(2),于是B的值就是myname is Michael Jackson。 |
范例2:
1 2 3 4 5 6 7 8 9 | 使用Makefile的命令,找出指定系统Shell指令的完整路径(类似 which 的功能)。 1,使用subst将环境变量PATH中每一个路径的分隔符冒号替换成空格: A= $(subst :,,$(PATH)) 2,将指定的Shell指令添加到每一个可能的路径后面:B= $(addsuffix /$(1),$(A)) 3,使用wildcard匹配所有正确的路径: C= $(wildcard $(B)) 将上述命令组合起来就能完成类似命令 which 的功能,暂且叫他为WHICH: WHICH = $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH))))注意:此处WHICH的定义只能是这样递归定义方式,而不能是直接定义方式。最后,使用call来给这个复杂的变量传递一个定制化的参数$(1),比如要获得系统命令 ps 的完整路径:$(call WHICH, ps ) |
(11)$(origin VAR)
功能:
1 | 顾名思义,该函数用来查看参数VAR的出处。 |
返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 参数VAR的出处,有如下几种情况: 1, undefined:表示变量VAR尚未被定义。 2,default:表示变量VAR是一个默认的内嵌变量,比如CC、MAKEFLAGS等。 3,environment:表示变量VAR是一个系统环境变量,比如PATH。 4, file :表示变量VAR在另一个Makefile 中被定义。 5, command line:表示变量VAR是一个在命令行定义的变量。 6,override:表示变量VAR在本Makefile定义并使用了override指示符。 7.automatic:表示变量VAR是一个自动化变量,比如@、^等等。 |
范例:
1 2 3 | ifeq (“$(origin V) "," command line") KBUILD_VERBOSE= $(V) endif |
判断变量V的出处,如果该变量来自命令行,则将KBUILD_VERBOSE赋为V的值(该范例摘选自Linux-3.9.8源码顶层Makefile) 。
(12)$(Shell COMMANDS)
功能:
1 | 在Makefile 中执行COMMANDS,此处的COMMANDS是一个或几个Shell命令,功能与在 Shell脚本中使用`COMMANDS`的效果相同。该函数返回这些命令的最终结果。 |
返回:
1 | 返回COMMANDS的执行结果,并把其中的回车符替换成空格符。 |
范例:
1 2 | contents := $(Shell cat file .txt) 使用 cat 命令显示 file .txt的内容,并将其中的回车符替换为空格之后赋给contents。注意到此处用的是直接定义方式而不是递归定义方式,这是为了防止后续再有对此变量的引用就不会有展开过程。这样可以防止规则命令行中的变量引用在命令行执行时展开的情况发生(因为展开 "Shell" 函数需要另外的Shell进程完成,影响命令的执行效率)。 |
19、实用make选项集锦
(1)指定要执行的Makefile文件:
1 2 3 | make -f Altmake make -- file Altmake make --makefile Altmake |
以上三种方式都可以用来执行一个普通命令的文件作为Makefile文件。在缺省的情况下不指定任何Makefile文件,则make会在当前目录下依次查找命名为GNUmakefile和Makefile 以及 makefile的文件。
(2)指定终极目标:make TARGET
1 | 所谓的终极目标省的是lM ldit中第一个出现的规则中的第一个目标(详细辉释祷参见Lk.3小节),是状的楚<br>个工程葳者苍读蝙锋过程的总的规门和目的。如果要执行论绫且所之外的其能昔通用所险确销的最的目的,<br>则可以找的行mtka的同时指定。在我们需要对程序的一部分进行编译或者仅仅对某几个程序进行编译而不是<br>完整地编译整个工程的时候,指定终极目标就很有用。 |
(3)强制重建所有规则中目标
1 2 | make -B make -always- make |
(4)指定Makefile 的所在路径:
1 2 | make -C dir / make --directory= dir / |
假如要执行的Makefile文件不在当前目录,可以使用该选项指定。这个选项一般用在一个Makefile内部调用另一个子Makefile 的场景(详见1.5.4小节中关于特殊变量的部分)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通