笔者上手一些大型项目时,常常会看不懂Makefile而造成一些困难,因此参考资料形成了一篇文章,从完全零基础过来的,用时也不多。文末附上一个笔者最近学习的rCore的Makefile,看完全篇文章后一定可以看懂的,本文也可用于一个简单的中文手册查询。
一些好的学习资料:
Makefile Tutorial By Example
【从零开始学Makefile】 从零开始学Makefile_哔哩哔哩_bilibili
Make/make.md · 岩木/CPP - Gitee.com
跟我一起写Makefile — 跟我一起写Makefile 1.0 文档 (seisman.github.io)
makefile有点像跟手动编译过程反着来,一步步去找依赖项
规则 的构成1 2 3 4 targets: prerequisites command command command
targets(目标):是文件名,通常一个规则只有一个targets
command(命令/方法):创建目标的一系列步骤,需要用tab开头,
prerequisites(依赖):也是文件名,可以有多个,用空格分隔,在运行目标的命令之前,这些文件需要存在。
命令和执行 命令执行 command其实就是执行shell的指令,默认一个command都是一个独立的shell 来执行,如果需要所有command都在一个shell执行,需要.ONESHELL
一个例子
1 2 3 4 5 6 7 8 9 10 11 all: cd .. echo `pwd` cd ..;echo `pwd` cd ..; \ echo `pwd`
command回显 command默认是先打印出语句,再执行内容,如果不需要先打印语句,在command前面加上@
也可以使用.SILENT + 文件名
,表示直接执行不打印
错误处理 如果一条规则当中包含多条Shell指令,每条指令执行完之后make都会检查返回状态,如果返回状态是0,则执行成功,继续执行下一条指令,直到最后一条指令执行完成之后,一条规则也就结束了。
如果过程中发生了错误,即某一条指令的返回值不是0,那么make就会终止执行当前规则中剩下的Shell指令。
例如
1 2 3 clean: rm main.o hello.o rm main.exe
这时如果第一条rm main.o hello.o出错,第二条rm main.exe就不会执行。类似情况下,希望make忽视错误继续下一条指令。在指令开头-
可以达到这种效果。
1 2 3 clean: -rm main.o hello.o -rm main.exe
也可以make -k
,即使遇到错误也能继续执行,如果想一次查看Make的所有错误,可以使用-k
目标 在终端执行命令时,如果没有将目标作为 make
参数提供给命令,将运行第一个目标
make只会在这两种情况运行targets及其命令
targets不存在,还未被创建
targets的依赖项更新了 (使用文件系统时间戳作为代理来确定是否有任何变化)
一个示例
1 2 3 4 5 6 7 8 9 blah: blah.o cc blah.o -o blah blah.o: blah.c cc -c blah.c -o blah.o blah.c: echo "int main() { return 0; }" > blah.c
以下 Makefile 最终运行所有三个目标。当您在终端中运行时 make
,它将构建一个按一系列步骤调用 blah
的程序:
Make 选择目标,因为第一个目标是默认目标 blah
blah
需要 blah.o
,因此搜索 blah.o
目标
blah.o
需要 blah.c
,因此搜索 blah.c
目标
blah.c
没有依赖项,因此运行命令 echo
然后运行该 cc -c
命令,因为所有 blah.o
依赖项都已完成
将运行 顶部 cc
命令,因为所有 blah
依赖项都已完成
就是这样: blah
是一个编译好的c程序
可以看出targets会事先搜索依赖项是否已经创建,如果一个targets没有依赖项,那么会直接运行
all
运行all后面的所有目标
1 2 3 4 5 6 7 8 9 10 11 all: one two three one: touch onetwo: touch twothree: touch threeclean: rm -f one two three
一个规则多个目标 当一个规则有多个目标时,将针对每个目标运行,$@
是包含目标名称的auto 变量
1 2 3 4 5 6 7 8 9 all: f1.o f2.o f1.o f2.o: echo $@
同一目标多条规则 同一目标可以对应多条规则。同一目标的所有规则中的依赖会被合并 。但如果同一目标对应的多条规则都写了更新的command,则会使用最新的一条更新方法,并且会输出警告信息。
同一目标多规则通常用来给多个目标添加依赖而不用改动已写好的部分。
1 2 3 4 5 6 7 8 input.o: input.cpp utility.inl g++ -c input.cppmain.o: main.cpp scene.h input.h test.h g++ -c main.cppscene.o: scene.cpp scene.h utility.inl g++ -c scene.cpp input.o main.o scene.o : common.h
同时给三个目标添加了一个依赖common.h,但是不用修改上面已写好的部分。
作用是可以在后面给目标添加依赖
为特定目标/模式 设置变量 特定目标
makefile的变量一般都是全局的,我们可以给特定目标设置只能他使用的变量
1 2 3 4 5 6 7 all: one = cool all: echo one is defined: $(one) other: echo one is nothing: $(one)
特定模式
1 2 3 4 5 6 7 %.c: one = cool blah.c: echo one is defined: $(one) other: echo one is nothing: $(one)
依赖 普通依赖 前面说过的这种形式都是普通依赖。直接列在目标后面。普通依赖有两个特点:
如果这一依赖是由其他规则生成的文件,那么执行到这一目标前 会先执行生成依赖的那一规则
如果任何一个依赖文件修改时间比目标晚(更新了),那么就重新生成目标文件
order-only依赖 依赖文件不存在时,会执行对应的方法生成,但依赖文件更新并不会导致目标文件的更新
如果目标文件已存在,order-only依赖中的文件即使修改时间比目标文件晚,目标文件也不会更新。
定义方法如下:
1 targets : normal- prerequisites | order - only - prerequisites
normal-prerequisites部分可以为空
变量
变量定义类似C语言里的宏展开,只是字符串替换,因此变量只有一种类型:字符串
变量只能是字符串。使用:=
或 =
$(x)
就是用x这个字符串变量的值来替换$(x)
${x}
与$(x)
等价
1 2 3 files := file1 file2some_file: echo "Look at this variable: " $(files)
会打印出Look at this variable: file1 file2
双$
符号
如果想表示$
这个符号
例子:
1 2 3 4 5 6 7 make_var = I am a make variableall: sh_var='I am a shell variable'; echo $$sh_var echo $(make_var)
对于echo $$sh_var
,实际上等价于echo $sh_var
而echo $(make_var)
,实际上等价于echo I am a make variable
通配符 wildcard(通配符)
wildcard
函数:用于匹配文件名模式,可与*
或?
配合使用
1 2 3 4 5 6 C_FILES := $(wildcard *.c) all: @echo "C files: $(C_FILES) "
*
:在文件系统中搜索匹配的文件名,匹配0或多个字符
注意不要在变量定义中使用*
当*
没有匹配任何文件时,它将保持原样(除非在wildcard
函数中运行)
建议*
永远与wildcard配合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 thing_wrong := *.o thing_right := $(wildcard *.o) all: one two three four one: $(thing_wrong) two: *.o three: $(thing_right) four: $(wildcard *.o)
%
当在匹配模式下使用时,会匹配字符串中的一个或多个字符
当在替换模式下使用时,会采用匹配的结果并将其替换在字符串中
常用于规则定义和特定函数
auto自动变量 $@
:本条规则的目标名
$?
:依赖中修改过的文件名
$^
:所有依赖文件名,文件名不会重复,不包含order-only依赖 (就是|
右边的)
$\*
:(简单理解)目标文件名的主干部分(即不包括后缀名 )
伪目标 .PHONY 如果一个目标并不是一个文件,则这个目标就是伪目标。例如前面的clean目标。如果说在当前目录下有一个文件名称和这个目标名称冲突了,则这个目标就没法执行。这时候需要用到一个特殊的目标 .PHONY,将上面的clean目标改写如下
1 2 3 4 .PHONY : cleanclean: rm block.o command.o input.o main.o scene.o test.o rm sudoku.exe
这样即使当前目录下存在与目标同名的文件,该目标也能正常执行。
伪目标的其他应用方式
如果一条规则的依赖文件没有改动,则不会执行对应的更新方法。如果需要每次不论有没有改动都执行某一目标的更新方法,可以把对应的目标添加到.PHONY的依赖中,例如下面这种方式,则每次执行make都会更新test.o,不管其依赖文件有没有改动
1 2 3 4 test .o: test .cpp test .h g++ -c test .cpp .PHONY: clean test .o
Makefile读取过程 GNU make分两个阶段来执行Makefile,第一阶段(读取阶段):
读取Makefile文件的所有内容
根据Makefile的内容在程序内建立起变量
在程序内构建起显式规则、隐式规则
建立目标和依赖之间的依赖图
第二阶段(目标更新阶段):
用第一阶段构建起来的数据确定哪个目标需要更新然后执行对应的更新方法
变量和函数的展开 (针对$
符号)如果发生在第一阶段,就称作立即展开 (第一阶段读到的时候就展开),否则称为延迟展开 。立即展开的变量或函数在第一个阶段,也就是Makefile被读取解析的时候就进行展开。延迟展开的变量或函数将会到用到的时候才会进行展开,有以下两种情况:
显式规则中,目标和依赖部分都是立即展开,在更新方法中延迟展开
一个例子:
1 2 3 4 5 6 7 8 9 a = ok file = $(a) all: @echo $(file) a = no
变量赋值 递归展开赋值(延迟展开) 第一种方式就是直接使用=,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量的时候再来进行处理得到变量值(Makefile执行的第二个阶段再进行变量展开得到变量值)
1 2 3 4 5 6 7 8 9 10 11 12 bar2 = ThisIsBar2No.1 foo = $(bar) foo2 = $(bar2) all: @echo $(foo) @echo $(foo2) @echo $(ugh) bar = $(ugh) ugh = Huh? bar2 = ThisIsBar2No.2
简单赋值(立即展开) 简单赋值使用:=或::=,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)
1 2 3 4 5 6 7 8 9 10 11 12 bar2 := ThisIsBar2No.1 foo := $(bar) foo2 := $(bar2) all: @echo $(foo) @echo $(foo2) @echo $(ugh) bar := $(ugh) ugh := Huh? bar2 := ThisIsBar2No.2
条件赋值 条件赋值使用?=,如果变量已经定义过了(即已经有值了),那么就保持原来的值,如果变量还没赋值过,就把右边的值赋给变量。
1 2 3 4 5 var1 = 100 var1 ?= 200all: @echo $(var1)
Shell运行赋值 使用!=
,运行一个Shell指令后将返回值赋给一个变量
1 2 gcc_version != gcc --version files != ls .
追加 使用 +=
用于追加
1 2 3 4 5 foo := start foo += moreall: echo $(foo)
取消变量 如果想清除一个变量,用以下方法,变量就会变为空
1 undefine <变量名> 如 undefine files, undefine obj
变量替换引用 语法:**$(var:a=b),意思是将变量var的值当中每一项 结尾**的a替换为b,直接上例子
1 2 3 4 files = main.cpp hello.cpp objs := $(files:.cpp=.o) objs := $(files:%.cpp=%.o)
变量覆盖 所有在Makefile中的变量,都可以在执行make时能过指定参数的方式进行覆盖。
1 2 3 OverridDemo := ThisIsInMakefileall: @echo $(OverridDemo)
如果直接执行
则上面的输出内容为ThisIsInMakefile ,但可以在执行make时指定参数:
1 2 3 make OverridDemo =ThisIsFromOutShell # 等号两边不能有空格 !! make OverridDemo =“This Is From Out Shell”
则输出OverridDemo的值是ThisIsFromOutShell或This Is From Out Shell。
用这样的命令参数会覆盖Makefile中对应变量的值,如果不想被覆盖,可以在变量前加上override 指令,override具有较高优先级,不会被命令参数覆盖
1 2 3 override OverridDemo := ThisIsInMakefileall: @echo $(OverridDemo)
这样即使命令行指定参数,也只会为ThisIsInMakefile
1 make OverridDemo =ThisIsFromOutShell
绑定目标的变量 Makefile中的变量一般是全局变量。也就是说定义之后在Makefile的任意位置都可以使用。但也可以将变量指定在某个目标的范围内,这样这个变量就只能在这个目标对应的规则里面保用
语法
1 2 3 4 target … : variable -assignment target … : prerequisites recipes …
例
1 2 3 4 5 6 7 8 9 10 11 12 var1 = Global Varfirst: all t2 all: var2 = Target All Var all: @echo $(var1) @echo $(var2) t2: @echo $(var1) @echo $(var2)
静态模式规则 静态模式就是用%
进行文件匹配来推导出对应的依赖。
语法
1 2 3 targets …: target-pattern (目标模式): prereq-patterns (依赖模式) … recipe …
一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 objects = foo.o bar.o all.oall: $(objects) $(objects) : %.o: %.call.c: echo "int main() { return 0; }" > all.c%.c: touch $@ clean: rm -f *.c *.o all
条件判断 ifeq判断两个值是否相等 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 version = 3.0ifeq ($(version) ,1.0) msg := 版本太旧了,请更新版本else ifeq ($(version) , 3.0) msg := 版本太新了,也不行else msg := 版本可以用endif msg = Otherifeq "$(OS) " "Windows_NT" msg = This is a Windows Platformendif ifeq '$(OS) ' 'Windows_NT'ifeq '$(OS) ' "Windows_NT"
还有ifneq
,用法相同,只是结果相反
ifdef判断变量是否已经定义 ifdef
不展开变量,他只是查看是否定义了变量
1 2 3 4 5 6 7 8 9 10 bar = foo = $(bar) all: ifdef foo echo "foo is defined" endif ifndef bar echo "but bar is not" endif
ifndef 判断一个变量是否没被定义1 2 3 ifndef FLAGS FLAGS = -finput-charset=utf-8endif
函数 调用函数的语法
$(fn, arguments)
或 ${fn, arguments}
,注意参数之间不要有空格,如果有空格的话将视为参数的一部分
字符替换与分析 subst 文本替换函数,返回替换后的文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $(subst target,replacement,text) --- 在text中,把target替换为replacement --- target 需要替换的内容 --- replacement 替换为的内容 --- text 需要处理的内容,可以是任意字符串 objs = main.o hello.o srcs = $(subst .o,.cpp,$(objs) ) headers = $(subst .cpp,.h,$(srcs) ) all: @echo $(srcs) @echo $(headers)
如果要替换空格或者逗号,使用变量
1 2 3 4 5 6 7 8 comma := , empty:= space := $(empty) $(empty) foo := a b c bar := $(subst $(space) ,$(comma) ,$(foo) ) all: @echo $(bar)
patsubst 模式替换, 返回替换后的文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $(patsubst pattern,replacement,text) --- pattern 需要替换的模式 --- replacement 需要替换为 --- text 待处理内容,各项内容需要用空格隔开 foo := a.o b.o l.a c.o one := $(patsubst %.o,%.c,$(foo) ) two := $(foo:%.o=%.c) three := $(foo:.o=.c)all: echo $(one) echo $(two) echo $(three)
strip 去除字符串头部和尾部的空格,中间如果连续有多个空格,则用一个空格替换,返回去除空格后的文本
1 2 3 4 5 6 $(strip string) --- string 需要去除空格的字符串 files = hello.cpp main.cpp test.cpp files2 = $(strip $(files) )
findstring 查找字符串,如果找到了,则返回对应的字符串,如果没找到,则反回空串
1 2 3 4 5 6 7 8 9 $(findstring find,string) --- find 需要查找的字符串 --- string 用来查找的内容 files = hello.cpp main.cpp test.cpp find = $(findstring hel,$(files) ) find = $(findstring HEL,$(files) )
filter 从文本中筛选出符合模式的内容并返回
1 2 3 4 5 6 7 $(filter pattern…,text) --- pattern 模式,可以有多个,用空格隔开 --- text 用来筛选的文本,多项内容需要用空格隔开,否则只会当一项来处理 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h files2 = $(filter %.o %.h,$(files) )
filter-out 与filter相反,过滤掉符合模式的,返回剩下的内容
1 2 3 4 5 6 7 $(filter -out pattern…,text) --- pattern 模式,可以有多个,用空格隔开 --- text 用来筛选的文本,多项内容需要用空格隔开,否则只会当一项来处理 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h files2 = $(filter -out %.o %.cpp,$(files) )
sort 将文本内的各项按字典顺序排列,并且移除重复项
1 2 3 4 5 6 $(sort list) --- list 需要排序内容 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h main.cpp hello.cpp files2 = $(sort $(files) )
word 用于返回文本中第n个单词 (注意下标从1开始的,不是0)
1 2 3 4 5 6 7 $(word n,text) --- n 第n个单词,从1开始,如果n大于总单词数,则返回空串 --- text 待处理文本 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h main.cpp hello.cpp files2 = $(word 3,$(files) )
wordlist 用于返回文本指定范围内的单词列表
1 2 3 4 5 6 $(wordlist start,end,text) --- start 起始位置,如果大于单词总数,则返回空串 --- end 结束位置,如果大于单词总数,则返回起始位置之后全部,如果start > end,什么都不返回 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h main.cpp hello.cpp files2 = $(wordlist 3,6,$(files) )
words 返回文本中单词数
1 2 3 4 5 6 $(words text) --- text 需要处理的文本 files = hello.cpp main.cpp test.cpp main.o hello.o hello.h main.cpp hello.cpp nums = $(words $(files) )
firstword 返回第一个单词
lastword 返回最后一个单词
文件名处理函数 dir 返回文件目录
1 2 3 4 5 6 $(dir files) --- files 需要返回目录的文件名,可以有多个,用空格隔开 files = src/hello.cpp main.cpp files2 = $(dir $(files) )
notdir 返回除目录部分的文件名
1 2 3 4 5 6 $(notdir files) --- files 需要返回文件列表,可以有多个,用空格隔开 files = src/hello.cpp main.cpp files2 = $(notdir $(files) )
suffix 返回文件后缀名,如果没有后缀返回空
1 2 3 4 5 6 7 $(suffix files) --- files 需要返回后缀的文件名,可以有多个,用空格隔开 files = src/hello.cpp main.cpp hello.o hello.hpp hello files2 = $(suffix $(files) )
basename 返回文件名除后缀的部分
1 2 3 4 5 6 7 $(basename files) --- files 需要返回的文件名,可以有多个,用空格隔开 files = src/hello.cpp main.cpp hello.o hello.hpp hello files2 = $(basename $(files) )
addsuffix 给文件名添加后缀
1 2 3 4 5 6 7 $(addsuffix suffix ,files) --- suffix 需要添加的后缀 --- files 需要添加后缀的文件名,可以有多个,用空格隔开 files = src/hello.cpp main.cpp hello.o hello.hpp hello files2 = $(addsuffix .exe,$(files) )
addprefix 给文件名添加前缀
1 2 3 4 5 6 7 $(addprefix prefix,files) --- prefix 需要添加的前缀 --- files 需要添加前缀的文件名,可以有多个,用空格隔开 files = src/hello.cpp main.cpp hello.o hello.hpp hello files2 = $(addprefix make/,$(files) )
join 将两个列表中的内容一对一连接,如果两个列表内容数量不相等,则多出来的部分原样返回,注意是有顺序之分的,比如下面这个例子如果改为files2 = $(join $(f2),$(f1))
,结果是相反的
1 2 3 4 5 6 7 8 9 $(join list1,list2) --- list1 第一个列表 --- list2 需要连接的第二个列表 f1 = hello main test f2 = .cpp .hpp files2 = $(join $(f1) ,$(f2) )
wildcard 返回符合通配符的文件列表
1 2 3 4 5 6 $(wildcard pattern) --- pattern 通配符 files2 = $(wildcard *.cpp) files2 = $(wildcard *) files2 = $(wildcard src/*.cpp)
realpath 返回文件的绝对路径
1 2 3 4 5 $(realpath files) --- files 需要返回绝对路径的文件,可以有多个,用空格隔开 f3 = $(wildcard src/*) files2 = $(realpath $(f3) )
abspath 返回绝对路径,用法同realpath,如果一个文件名不存在,realpath不会返回内容,abspath则会返回一个当前文件夹一下的绝对路径
条件函数 if if
检查第一个参数是否为非空 。如果是这样,则运行第二个参数,否则运行第三个参数
1 2 3 4 5 6 7 $(if condition,then-part[,else-part]) --- condition 条件部分 --- then-part 条件为真时执行的部分 --- else-part 条件为假时执行的部分,如果省略则为假时返回空串 files = src/hello.cpp main.cpp hello.o hello.hpp hello files2 = $(if $(files) ,有文件,没有文件)
or 返回条件中第一个不为空的部分
1 2 3 4 5 6 7 8 $(or condition1[,condition2[,condition3…]]) f1 = f2 = f3 = hello.cpp f4 = main.cpp files2 = $(or $(f1) ,$(f2) ,$(f3) ,$(f4) )
and 如果条件中有一个为空串,则返回空,如果全都不为空,则返回最后一个条件
1 2 3 4 5 6 7 8 $(and condition1[,condition2[,condition3…]]) f1 = 12 f2 = 34 f3 = hello.cpp f4 = main.cpp files2 = $(and $(f1) ,$(f2) ,$(f3) ,$(f4) )
intcmp 比较两个整数大小,并返回对应操作结果(GNU make 4.4以上版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]]) --- lhs 第一个数 --- rhs 第二个数 --- lt-part lhs < rhs时执行 --- eq-part lhs = rhs时执行 --- gt-part lhs > rhs时执行 --- 如果只提供前两个参数,则lhs == rhs时返回数值,否则返回空串 参数为lhs,rhs,lt-part时,当lhs < rhs时返回lt-part结果,否则返回空 参数为lhs,rhs,lt-part,eq-part,lhs < rhs返回lt-part结果,否则都返回eq-part结果 参数全时,lhs < rhs返回lt-part,lhs == rhs返回eq-part, lhs > rhs返回gt-part @echo $(intcmp 2,2,-1,0,1)
file 读写文件
1 2 3 4 5 6 7 8 9 10 11 12 $(file op filename[,text]) --- op 操作 > 覆盖 >> 追加 < 读 --- filename 需要操作的文件名 --- text 写入的文本内容,读取是不需要这个参数 files = src/hello.cpp main.cpp hello.o hello.hpp hello write = $(file > makewrite.txt,$(files) ) read = $(file < makewrite.txt)
foreach 对一列用空格隔开的字符序列中每一项进行处理,并返回处理后的列表(将一个单词列表(用空格分隔)转换为另一个单词列表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $(foreach each,list,process) --- each list中的每一项 --- list 需要处理的字符串序列,用空格隔开 --- process 需要对每一项进行的处理 foo := who are you bar := $(foreach wrd,$(foo) ,$(wrd) !) all: @echo $(bar) list = 1 2 3 4 5 result = $(foreach each,$(list) ,$(addprefix cpp,$(addsuffix .cpp,$(each) ) ))
第二个例子作用类似C/C++中的循环
1 2 3 4 5 6 7 8 9 int list[5 ] = {1 , 2 , 3 , 4 , 5 };int result[5 ];int each;for (int i = 0 ; i < 5 ; i++) { each = list[i]; result[i] = process (each); }
call
Make支持创建函数,可以通过创建变量来定义函数,但使用参数 $(0)
、 $(1)
等,$(0)
是变量,而 $(1)
、 $(2)
等是参数
将一些复杂的表达式写成一个变量,用call可以像调用函数一样进行调用。类似于编程语言中的自定义函数。在函数中可以用$(n)来访问第n个参数
1 2 3 4 5 6 7 8 9 $(call funcname,param1,param2,…) --- funcname 自定义函数(变量名) --- 参数至少一个,可以有多个,用逗号隔开 sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)all: @echo $(call sweet_new_fn, go, tigers)
value 对于不是立即展开的变量,可以查看变量的原始定义;对于立即展开的变量,直接返回变量值
1 2 3 4 5 6 7 8 $(value variable) var = value function test var2 = $(var) var3 := $(var) all: @echo $(value var2) @echo $(value var3)
查看一个变量定义来源
1 2 3 4 5 6 7 8 9 10 $(origin variable) var2 = origin function all: @echo $(origin var1) @echo $(origin CC) @echo $(origin JAVA_HOME) @echo $(origin var2) @echo $(origin @)
flavor 查看一个变量的赋值方式
1 2 3 4 5 6 7 8 $(flavor variable) var2 = flavor function var3 := flavor funcitonall: @echo $(flavor var1) @echo $(flavor var2) @echo $(flavor var3)
eval 可以将一段文本生成Makefile的内容
1 2 3 4 5 6 7 8 $(eval text) define eval_target = eval: @echo Target Eval Testendef $(eval $(eval_target) )
以上,运行make时将会执行eval目标,输出Target Eval Test
shell 用于执行Shell命令
1 2 files = $(shell ls *.cpp) $(shell echo This is from shell function)
let 将一个字符串序列中的项拆开放入多个变量中,并对各个变量进行操作(GNU make 4.4以上版本),有点像rust里的解构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $(let var1 [var2 ...],[list],proc) --- var 变量,可以有多个,用空格隔开 --- list 待处理字符串,各项之间空格隔开 --- proc 对变量进行的操作,结果为let的返回值 将list中的值依次一项一项放到var中,如果var的个数多于list项数,那多出来的var是空串。如果 var的个数小于list项数,则先依次把前而的项放入var中,剩下的list所有项都放入最后一个var中 list = a b c d letfirst = $(let first second rest,$(list) ,$(first) ) letrest = $(let first second rest,$(list) ,$(rest) ) reverse = $(let first rest,$(1) ,$(if $(rest) ,$(call reverse,$(rest) ) )$(first) )all: ; @echo $(call reverse,d c b a)
信息提示函数 error 提示错误信息并终止make执行
1 2 3 4 5 6 7 $(error text) --- text 提示信息 EXIT_STATUS = -1ifneq (0, $(EXIT_STATUS) ) $(error An error occured! make stopped!) endif
warning 提示警告信息,make不会终止
1 2 3 4 5 $(warning text) ifneq (0, $(EXIT_STATUS) ) $(warning This is a warning message) endif
info 输出一些信息
1 2 3 4 $(info text…) $(info 编译开始.......) $(info 编译结束)
实战 试试分析一下这个rust的makefile把,来自rCore
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 TARGET := riscv64gc-unknown-none-elf MODE := release APP_DIR := src/bin TARGET_DIR := target/$(TARGET) /$(MODE) BUILD_DIR := build OBJDUMP := rust-objdump --arch-name=riscv64 OBJCOPY := rust-objcopy --binary-architecture=riscv64 PY := python3ifeq ($(MODE) , release) MODE_ARG := --releaseendif BASE ?= 0 CHAPTER ?= 0 TEST ?= $(CHAPTER) ifeq ($(TEST) , 0) APPS := $(filter -out $(wildcard $(APP_DIR) /ch*.rs) , $(wildcard $(APP_DIR) /*.rs) )else ifeq ($(TEST) , 1) APPS := $(wildcard $(APP_DIR) /ch*.rs) else TESTS := $(shell seq $(BASE) $(TEST) ) ifeq ($(BASE) , 0) APPS := $(foreach T, $(TESTS) , $(wildcard $(APP_DIR) /ch$(T) _*.rs) ) else ifeq ($(BASE) , 1) APPS := $(foreach T, $(TESTS) , $(wildcard $(APP_DIR) /ch$(T) b_*.rs) ) else APPS := $(foreach T, $(TESTS) , $(wildcard $(APP_DIR) /ch$(T) *.rs) ) endif endif ELFS := $(patsubst $(APP_DIR) /%.rs, $(TARGET_DIR) /%, $(APPS) ) binary: @echo $(ELFS) @if [ ${CHAPTER} -gt 3 ]; then \ cargo build $(MODE_ARG) ;\ else \ CHAPTER=$(CHAPTER) python3 build.py ;\ fi @$(foreach elf, $(ELFS) , \ $(OBJCOPY) $(elf) --strip -all -O binary $(patsubst $(TARGET_DIR) /%, $(TARGET_DIR) /%.bin, $(elf) ) ; \ cp $(elf) $(patsubst $(TARGET_DIR) /%, $(TARGET_DIR) /%.elf, $(elf) ) ;)disasm: @$(foreach elf, $(ELFS) , \ $(OBJDUMP) $(elf) -S > $(patsubst $(TARGET_DIR) /%, $(TARGET_DIR) /%.asm, $(elf) ) ;) @$(foreach t, $(ELFS) , cp $(t) .asm $(BUILD_DIR) /asm/;) pre: @mkdir -p $(BUILD_DIR) /bin/ @mkdir -p $(BUILD_DIR) /elf/ @mkdir -p $(BUILD_DIR) /app/ @mkdir -p $(BUILD_DIR) /asm/ @$(foreach t, $(APPS) , cp $(t) $(BUILD_DIR) /app/;) build: clean pre binary @$(foreach t, $(ELFS) , cp $(t) .bin $(BUILD_DIR) /bin/;) @$(foreach t, $(ELFS) , cp $(t) .elf $(BUILD_DIR) /elf/;) clean: @cargo clean @rm -rf $(BUILD_DIR) all: build .PHONY : elf binary build clean all