1、Makefile介绍
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释Makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
- make是一条命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建
2、通过一个简单的例子来认识Makefile
例如存在一个源代码:
使用vim打开Makefile进行编辑:上述是利用test.c直接编译生成可执行程序test
但通常需要将编译与链接分开写:
退出vim,执行指令make,即可完成test.c的编译:
规则:一个Makefile文件由一条条规则构成,一条规则结构如下:
target...(目标): prerequisites...(依赖) recipe(方法) ...... ......//注意:前面必须是一个tab
第二种写法:
target...(目标): prerequisites...(依赖);recipe(方法);...
make主要用于处理C和C++的编译工作,但不只能处理C和C++,所有编译器/解释器能在命令行终端运行的编程语言都可以处理(例如Java、Python、Golang….)。make也不只能用来处理编程语言,所有基于一些文件(依赖)的改变去更新另一些文件(目标)的工作都可以做。
3、Makefile文件的命名和指定
- make会在当前目录自动查找makefile文件,查找顺序为GNUmakefile ->makefile ->Makefile
- GNUmakefile:不建议使用,因为只有GNU make会识别,其他版本的make(如BSDmake,Windows nmake等)不会识别,如果只给GNU make使用的情况
- makefile:可以使用,GNU make和其他版本make识别
- Makefile:最常用,强烈建议使用
如果执行make没有上述的文件,则会被报错,或者可以使用手动指定文件名:
#-f方式
make -f mkfile #make -f 文件名
#--file方式
make --file=mkfile #make --file=文件名
注意:make时指定文件的优先级最高,即有了Makefile但指定了文件,仍是使用指定的文件。
4、Makefile文件内容组成
- 一个Makefile文件通常由五种类型的内容组成:显式规则、隐式规则、变量定义、指令和注释
- 显式规则(exp/icit rules):显式指明何时以及如何生成或更新目标文件,显式规则包括目标、依赖和更新方法三个部分
- 隐式规则(implicit rules):根据文件自动推导如何从依赖生成或更新目标文件
- 变量定义(variable definitions):定议变量并指定值,值都是字符串,类似C语言中的宏定义(#define),在使用时将值展开到引用位置
- 指令(directives):在make读取Makefile的过程中做一些特别的操作,包括:
- 读取(包含)另一个makefile文件(类似C语言中的#include)
- 确定是否使用或略过makefile文件中的一部分内容(类似C语言中的#if)
- 定义多行变量
- 注释(comments):一行当中#后面的内容都是注释,不会被make执行。make当中只有单行注释。如果需要用到#而不是注释,用\#。
5、实现一个计算器
如下已经写了一个计算器的加减乘除的实现和头文件,主体在main.c,现在需要使用Makefile进行编译生成可执行程序
Makefile内部:
执行make进行编译,make clean删除中间文件,得到可执行文件
注意:
- 包含库文件的头文件时不需要加入到依赖文件列表中,自己创建的头文件需要加入到依赖文件列表中;
- 指令:gcc -MM .c文件,会将自己包含的自己创建的头文件显示出来(间接包含和直接包含)
- 如果依赖文件发生了改变,那么再次执行make时目标文件会被更新,依赖文件没有发生改变,再次执行make时目标文件不会发生改变,相应的规则也不会执行。(这也是为什么编译和链接分两次走的原因:因为一个项目中源文件非常多,如果直接使用.c编译成可执行,那么假如有一个源文件发生更改,再次make时则其他依赖的源文件也需要重新编译;如果使用编译和链接分两步,则可执行文件依赖的是众多的.o文件,其中一个源文件发生更改时,只需要将该源文件重新编译成.o文件,不会使其他源文件重新编译成.o)
- Makefile中从上到下的第一个目标文件是作为最终目标,需要注意的是,当第一个目标文件被生成时,即使后面有未生成的目标文件,也不会继续向后执行生成。
当需要同时编译多个程序时,可以利用这个性质,创立一个依赖关系,all作为第一个目标文件,需要编译的多个程序作为依赖文件列表,当多个程序完成编译时,才会执行all的依赖方法(实际上all没有创建) - 上面的clean,既不是最终目标文件,也不是生成最终目标目标文件的依赖文件,正常make使不会执行该目标的,需要执行该目标,需要手动指定,即 make clean;
- 最终目标可以指定:.DEFAULT_GOAL = 目标
.DEFAULT_GOAL = main all: @echo all main: @echo main
- 在依赖方法的指令前面加上@可以关闭回显
- 依赖文件也有可能不存在,或者这个依赖文件是另一个依赖关系的目标文件,那么这个目标文件也有自己的依赖文件和指令,只有得到这个目标文件,这个目标文件才能作为上一层的依赖文件去形成上一层的目标文件,有时候这种关系是多层次的,类似于堆栈的过程。
- make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make不会提示。
6、伪目标
不生成或者更新文件的目标称之为伪目标(一般用于执行删除等操作)
与普通目标的区别:普通目标对应的就是文件名,对应规则就是生成或更新该目标文件,伪目标不生成目标文件,只是用于进行其他操作,例如上面的clean是用于删除中间文件的。
注意:定义伪目标时,需要用 .PHONY来声明,向make说明该目标是伪目标
.PHONY:clean
clean:
rm -rf *o
.PHONY的另一种用法:上面说过的,如果一个目标文件的依赖文件发生更改,那么再次make时会更新目标文件,如果依赖文件没有更改,再次make就不会再次更新目标文件;
如果想要每一次make时,目标文件都进行更新,不管依赖文件是否更改,就可以将该目标使用.PHONY进行声明,每次make都会进行更新。
7、依赖类型
普通依赖
前面说过的这种形式都是普通依赖。直接列在目标后面。普通依赖有两个特点:
- 如果这一依赖是由其他规则生成的文件,那么执行到这一目标前会先执行生成依赖的那一规则
- 如果任何一个依赖文件修改时间比目标晚,那么就重新生成目标文件
order-only依赖
- 依赖文件不存在时,会执行对应的方法生成,但依赖文件更新并不会导致目标文件的更新
- 如果目标文件已存在,order-only依赖中的文件即使修改时间比目标文件晚,目标文件也不会更新。
定义如下:targets:normal-prerequisites | order-only-prerequisites
normal-prerequisites部分可以省略,|左边是普通依赖,|右边是order-only依赖。
8、更新方法
上面提到的一条规则中,存在方法:
target...(目标):prerequisites...(依赖)
recipe(方法)
...
...
更新方法实际上是一些Shell指令,通常以Tab开头,或直接放在目标-依赖列表后面,用分号隔开。这些指令都需要交给Shell执行,所以需要符合Shell语法。默认使用的Shell是sh,Windows上如果没有安装sh.exe的话会自动查找使用cmd.exe之类的终端。
可以通过SHELL变量手动指定Shell,例如:SHELL = ……
默认的执行方式为一条指令重新调用一个Shell进程来执行。有时为了提高性能或其他原因,想让这个目标的所有指令都在同一进程中执行,可以在Makefile中添加 .ONESHELL
指令前加@可以取消回显:
错误处理:
- 如果一条规则当中包含多条Shell指令,每条指令执行完之后make都会检查返回状态,如果返回状态是0,则执行成功,继续执行下一条指令,直到最后一条指令执行完成之后,一条规则也就结束了。
- 如果过程中发生了错误,即某一条指令的返回值不是0,那么make就会终止执行当前规则中剩下的Shell指令。
- 如果我们想要忽略错误继续执行下一步指令,可以在指令开头添加-
clean: -rm -rf main.o hello.o -rm -rf main.exx #原来不带-的话,如果main.o不存在就会出错终止 #现在带-的话,如果main.o不存在也会不终止,会继续向后执行后面的指令
9、使用变量简化Makefile
9.1 变量的应用
Makefile中的变量有点类似C语言中的宏定义,即用一个名称表示一串文本。但与C语言宏定义不同的是,Makefile的变量值是可以改变的。变量定义之后可以在目标、依赖、方法等Makefile文件的任意地方进行引用。
注意:Makefile中的变量只有一种类型:字符串
变量可以用来表示:文件名序列、编译选项、需要运行的程序、需要进行操作的路径……
9.2 变量定义与引用方式
定义方式:
- 变量名=变量值
- 变量名:=变量值
- 变量名::=变量值
files=main.cpp hello.cpp var::=main.o
注意:变量名区分大小写,可以是任意字符串,但不能包含":"、"#"、"="
使用方法:
- $(变量名)
- ${变量名}
var=main.c main.o:${var} ...
注意:
- 如果变量名只有一个字符,使用时可以不用括号,如$a,$b,但不建议这样用,不管是否只有一个字符都写成$() / ${} 的形式。
- 如果变量值需要使用$符号时,可以使用$$来获取一个符号( $$ 转义为一个 $):
10、Makefile读取过程
GNU make分两个阶段来执行Makefile,
第一阶段(读取阶段):
- 读取Makefile文件的所有内容
- 根据Makefile的内容在程序内建立起变量
- 在程序内构建起显式规则、隐式规则
- 建立目标和依赖之间的依赖图
第二阶段(目标更新阶段):
- 用第一阶段构建起来的数据确定哪个目标需要更新然后执行对应的更新方法
- 变量和函数的展开如果发生在第一阶段,就称作立即展开,否则称为延迟展开。立即展开的变量或函数在第一个阶段,也就是Makefile被读取解析的时候就进行展开。延迟展开的变量或函数将会到用到的时候才会进行展开,有以下两种情况:
- 在一个立即展开的表达式中用到
- 在第二个阶段中用到
显式规则中,目标和依赖部分都是立即展开,在更新方法中延迟展开
11、变量赋值
11.1 递归展开赋值(延迟展开)
第一种方式就是直接使用=,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量(执行时展开)的时候再来进行处理得到变量值(Makefile执行的第二个阶段再进行变量展开得到变量值)
11.2 简单赋值(立即展开)
简单赋值使用 := 或 ::= ,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)
当使用:=在第一阶段展开时,如果展开的变量还没有定义,展开时会替换成空。
11.3 条件赋值
条件赋值使用 ?= ,如果变量已经定义过了(已经有值了),那么就保持原来的值,如果变量还没赋值过,就把右边的值赋值给变量。
11.4 追加
使用 += 在变量已有的基础上追加内容
11.5 Shell运行赋值
使用 != ,运行一个Shell指令后将返回值赋给一个变量
11.6定义多行变量
#语法
define 变量名 # 默认为=
……
……
……
……
endef
define 变量名:= # :=
……
……
……
……
endef
define 变量名+= # +=
……
……
……
……
endef
11.7取消定义
使用undefine来取消定义
var=1
undefine var
all:
echo $(var)
11.8系统环境变量的使用
指令:set,查看环境变量
在Makefile中使用:跟其他变量的使用一样,使用$符号。
11.9 变量替换引用
语法:$(var:a=b),意思是将变量var的值当中每一项结尾的a替换为b
语法:$(var:%a=%b),使用模式匹配进行替换
11.10 变量覆盖
在Makefile定义的变量,可以在make时进行覆盖,需要注意的是,覆盖值存在空格时需要双引号引用起来。
定义变量时前面加上 override ,可以禁止make时进行变量覆盖。
11.11 绑定目标的变量
Makefile中的变量一般是全局变量。也就是说定义之后在Makefile的任意位置都可以使用。但也可以将变量指定在某个目标的范围内,这样这个变量就只能在这个目标对应的规则里面保用。
#语法
target: 变量定义 #这样这个变量就只能在target指定的目标下使用了
target: prerequisites
recipes
……
……
11.12 自动变量
- $@ : ①本条规则的目标名;②如果目标是归档文件的成员,则为归档文件名;③在多目标的模式规则中,为导致本条规则方法执行的那个目标名;
- $< : 本条规则的第一个依赖名称
- $? : 依赖中修改时间晚于目标文件修改时间的所有文件名,以空格隔开
- $^ : 所有依赖文件名,文件名不会重复,不包含order-only依赖
- $+ : 类似上一个, 表示所有依赖文件名,包括重复的文件名
- $| : 所有order-only依赖文件名
- $* : (简单理解)目标文件名的主干部分(即不包括后缀名)
- $% : 如果目标不是归档文件,则为空;如果目标是归档文件成员,则为对应的成员文件名
以下变量对应上述变量,D为对应变量所在的目录,结尾不带/,F为对应变量除去目录部分剩下文件名
- $(@D)
- $(@F)
- $(*D)
- $(*F)
- $(%D)
- $(%F)
- $(<D)
- $(<F)
- $(^D)
- $(^F)
- $(+D)
- $(+F)
- $(?D)
- $(?F)
11.13 二次展开
前面说过依赖中的变量都是在Makefile读取阶段(第一阶段)立即展开的。如果想让依赖的的变量延迟展开(第二阶段),可以使用.SECONDEXPANSION:,添加之后,在依赖中使用变量时用$$,可以让变量在第二阶段进行二次展开,从而达到延迟展开的效果。(这种写法只限于变量在依赖中展开的情况)
11.14 多目标与多规则
显式规则中一条规则可以有多个目标,多个目标可以是相互独立的目标,也可以是组合目标,用写法来区分
独立多目标:
相互独立的多目标与依赖之间直接用 :,常用这种方式的有以下两种情况
1、只需要写目标和依赖,不需要写方法的时候
file1.o file2.o file3.o:common.h
#等价与下面的写法:
file1.o:common.h
file2.o:common.h
file3.o:common.h
2、生成(更新)目标的方法写法一样的,只是依赖与目标不一样时。之前写的Makefile中,
objs=block.o command.o input.o main.o scene.o test.o
target: $(objs)
g++ -o $@ $^
blocl.o: block.cpp common.h block.h color.h
g++ -c $<
command.o: command.cpp scene.h common.h block.h command.h
g++ -c $<
input.o: input.cpp common.h utility.inl
g++ -c $<
main.o: main.cpp scene.h common.h block.h command.h input.h
g++ -c $<
scene.o: scenen.cpp common.h scene.h block.h command.h utility.inl
g++ -c $<
test.o: test.cpp test.h scene.h common.h block.h command.h
g++ -c $<
可以写成这样,这样会对每个目标分别执行一次方法(依赖会通过隐式推导):
objs=block.o command.o input.o main.o scene.o test.o
target: $(objs)
g++ -o $@ $^
$(objs):
g++ -c $(@:%.o=%.cpp)
组合多目标:
意思就是将多个目标当成一个整体,方法只执行一次,所以需要在方法中添加每个目标的更新方法
组合多目标的多目标与依赖之间用 &:
objs=block.o command.o input.o main.o scene.o test.o
target: $(objs)
g++ -o $@ $^
$(objs) &:
g++ -c $(@:%.o = %.cpp)
g++ -c block.cpp
g++ -c command.cpp
多规则:
同一个目标,规则重载的情况下,先合并依赖,再尝试执行最后一条规则的方法,方法为空时,则执行上一条规则的方法。
all: t1 t2
@echo all-1
@echo $^
all: t3 t4
@echo all-2
@echo $^
t1:
@echo $@ recipe executing...
t2:
@echo $@ recipe executing...
t3:
@echo $@ recipe executing...
t4:
@echo $@ recipe executing...
#输出:all-2
t3 t4 t1 t2
all: t1 t2
@echo all-1
@echo $^
all: t3 t4
t1:
@echo $@ recipe executing...
t2:
@echo $@ recipe executing...
t3:
@echo $@ recipe executing...
t4:
@echo $@ recipe executing...
#输出:all-1
t1 t2 t3 t4
静态模式:
独立多目标可以简化Makefile的书写,但是不利于将各个目标的依赖分开,让目标文件根据各自的依赖进行更新。静态模式可以在一定程度上改进依赖分开问题。静态模式就是用%进行文件匹配来推导出对应的依赖。
语法:
targets...: target-pattern(目标模式): prereq-pattrens(依赖模式)...
recipe
...
...
例子:
block.o : %.o : %.cpp %.h
g++ -c $<
block.o为目标,%.o为目标模式,%.cpp,%.h为依赖模式,对于这一条规则,%.o代表的是目标文件block.o,所以这里的%匹配的是block,因此,%.cpp表示block.cpp,%.h代表block.h,所以block.o: %.o: %.cpp %.h表示的意思同下面这种写法
block.o: block.cpp block.h
12、指定依赖搜索路径
make默认在Makefile文件所在的目录下查找依赖文件,如果找不到,就会报错。这时候就需要手动指定搜索路径,用VPATH变量或vpath指令。
VPATH用法:
VPATH = <dir1>:<dir2>:<dir3>...
#例如:VPATH = include:src
多个目录之间冒号隔开,这时make会在VPATH指定的这些目录里面查找依赖文件。
vpath指令用法:
vpath比VPATH使用更灵活,可以指定某个类型的文件在哪个目录搜索。
vpath <pattern> <direcotries>
#例如
vpath %.h include #.h文件在include目录下查找
vpath %.h include:headers #.h文件在include或headers文件下查找
vpath % src #所有文件都在src下查找
vpath hello.cpp src #hello.cpp 文件在src查找
13、条件判断
使用条件指令可以让make执行或略过Makefile文件中的一些部分
- ifdef 判断一个变量是否已定义,定义了就执行
- ifndef 判断一个变量是否没被定义,没定义就执行
- ifeq 判断两个值是否相等,相等就执行
- ifneq 判断两个值是否不等,用法及参数同ifeq,只是不相等就执行
14、文本处理函数
在Makefile中调用函数的写法为:
$(function arguments) 或 ${function arguments}$(function arg1,$(arg2),arg3...) 注意:多参数之间用逗号隔开不要有空格
14.1 字符替换与分析
subst:文本替换函数,返回替换后的文本
${subst target,replacement,text} ---用replacement替换text中的target ---target需要替换的内容 ---text需要处理的内容,可以是任意字符串
patsubst:模式替换,返回替换后的文本
$(patsubst pattern,replacement,text) ---pattern 需要替换的模式 ---replacement 需要替换为 ---text 待处理内容,各项内容需要用空格隔开
strip:去除字符串头部和尾部的空格,中间如果连续有多个空格,则用一个空格替换,返回去除空格后的文本
&(strip string) --- string 需要去除空格的字符串
findstring:查找字符串,如果找到了,则返回对应的字符串,如果没找到,则返回空串
$(finding find,string) ---find 需要查找的字符串 ---string 用来查找的内容
filter:从文本中筛选出符合模式的内容并返回
$(filter pattern...,text) ---pattern 模式,可以有多个,用空格隔开 ---text 用来筛选的文本,多项内容需要用空格隔开,否则只会当一项来处理
filter-out:与filter相反,过滤掉符合模式的,返回剩下的内容
$(filter-out pattern...,text) ---pattern 模式,可以有多个,用空格隔开 ---text 用来筛选的文本,多项内容需要用空格隔开,否则只会当一项来处理
sort:将文本内的各项按字典顺序排列,并且移除重复项
$(sort list) ---list 需要排序的内容
word:用于返回文本中第n个单词
$(word n,text) ---n 第n个单词,从1开始,如果n大于总单词数,则返回空串 ---text 待处理文本
wordlist:用于返回文本指定范围内的单词列表
$(wordlist start,end,text) ---start 起始位置,如果大于单词总数,则返回空串 ---end 结束位置,如果大于单词总数,则返回起始位置之后的全部
words:返回文本中单词数
$(words text) ---text 需要处理的文本
firstword:返回第一个单词
$(firstword text)
lastword:返回最后一个单词
$(lastword text)
14.2 文件名处理函数
dir:返回文件目录
$(dir files) ---files 需要返回目录的文件名,可以有多个,用空格隔开
notdir:返回除目录部分的文件名
$(notdir files) ---files 需要返回文件列表,可以有多个,用空格隔开
suffix:返回文件后缀名,如果没有后缀返回空
$(suffix files) ---files 需要返回后缀的文件名,可以有多个,用空格隔开
basename:返回文件名除后缀的部分
$(basename files) ---files 需要返回的文件名,可以有多个,用空格隔开
addsuffix:给文件名添加后缀
$(realpath files) ---files 需要返回绝对路径的文件,可以有多个,用空格隔开
$(addsuffix suffix,files) ---suffix 需要添加的后缀 ---files 需要添加后缀的文件名,可以有多个,用空格隔开
addprefix:给文件名添加前缀
$(addprefix prefix,files) ---prefix 需要添加的前缀 ---files 需要添加前缀的文件名,可以有多个,用空格隔开
join:将两个列表中的内容一对一连接(list2接在list1后面),如果两个列表内容数量不相等,则多出来的部分原样返回
$(join list1,list2) ---list1 第一个列表 ---list2 需要连接的第二个列表
wildcard:返回符合通配符的文件列表
$(wildcard pattern) ---pattern 通配符*
realpath:返回文件的绝对路径
$(realpath files) ---files 需要返回绝对路径的文件,可以有多个,用空格隔开
abspath:返回绝对路径,用法同realpath,如果一个文件名不存在,realpath不会返回内容,abspath则会返回一个当前文件夹一下的绝对路径
$(abspath files)
14.3 条件函数
if:条件判断,如果条件展开不是空串,则返回真的部分,否则返回假的部分
$(if condition,then-part[,else-part]) ---condition 条件部分 ---then-part 条件为真时执行的部分 ---else-part 条件为假时执行的部分,如果省略则为假时返回空串
or:返回条件中第一个不为空的部分
$(or conditiona1[,condition2[,condition3...]])
and:如果条件中有一个为空串,则返回空,如果全都不为空,则返回最后一个条件
$(and condition1[,condition2[,condition3...]])
intcmp:比较两个整数大小,并返回对应操作结果(GNU make 4.4以上版本)
$(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结果,lhs==rhs时返回eq-part,否则返回空串 参数全时,lhs<rhs返回lt-part,lhs == rhs返回eq-part,lhs>rhs返回gt-part
14.4 file
file:读写文件
$(file op filename[,text]) ---op 操作 > 覆盖 >> 追加 < 读 --- filename 需要操作的文件名 --- text 写入的文本内容,读取是不需要这个参数
14.5 foreach
foreach:对一列用空格隔开的字符序列中每一项进行处理,并返回处理后的列表
执行过程:把list中的每一项依次赋值给each,类型循环操作$(foreach each,list,process) --- each list中的每一项 ---list 需要处理的字符串序列,用空格隔开 ---process 需要对每一项进行的处理
14.6 call
call:将一些复杂的表达式写成一个变量,用call可以像调用函数一样进行调用。类似于编程语言中的自定义函数。在函数中可以用$(n)来访问第n个参数
$(call funcname,paraml,param2,...) ---funcname 自定义函数(变量名) ---参数至少一个,可以有多个,用逗号隔开
14.7 value
value:对于不是立即展开的变量,可以查看变量的原始定义;对于立即展开的变量,直接返回变量值
$(value variable)
14.8 origin
origin:查看一个变量定义来源
$(origin variable)
- 变量没定义--返回undefined
- 默认变量--返回default
- 环境变量--返回environment
- Makefile中定义的变量--返回file
- 自动变量--返回automatic
14.9 flavor
flavor:查看一个变量的赋值方式
$(flavor variable)
- 变量没定义--返回undefined
- 不是立即展开--返回recursive(递归展开)
- 立即展开--返回simple(简单赋值)
14.10 eval
eval:可以将一段文本生成Makefile的内容
$(eval text)
相当于将
eval:
@echo Target Eval Test替换到
$(eval $(eval_target))
14.11 shell
shell:用于执行Shell命令
files = $(shell ls *.cpp) $(shell echo This is from shell function)
14.12 let
let:将一个字符串序列中的项拆开放入多个变量中,并对各个变量进行操作(GNU make 4.4以上版本)
$(let varl [var2 ...],[list],proc) ---var 变量,可以有多个,用空格隔开 ---list 待处理字符串,各项之间空格隔开 ---proc 对变量进行的操作,结果为let的返回值 将list中的值依次一项一项放到var中,如果var的个数多于list项数,剩下的变量var就会是空串 var的个数小于list项数,则先依次把前而的项放入var中,剩下的list放入最后一个变量
14.13 信息提示函数
error:提示错误信息并终止make执行,提示行数
$(error text) ---text 提示信息
warning:提示警告信息,make不会终止,提示行数
$(warning text)
info:输出一些信息
$(info text)
15、自动推导与隐式规则
Makefile中有一些生成目标文件的规则使用频率非常高,比如由.c或.cpp文件编译成.o文件,这样的规则在make中可以自动推导,所以可以不用明确写出来,这样的规则称为隐式规则(除了后缀的文件名要相同)。
在Makefile中,
CC
变量用于指定 C 语言编译器的命令。它通常会被设置为编译器的可执行文件名,例如gcc
(GNU C Compiler)或clang
等。通过设置
CC
变量,用户可以灵活地更改编译器,而不需要在Makefile的每个编译命令中都手动更改。这种方式使得Makefile更加通用和可移植。
一些make预定义的隐式规则:
从.c 到 .o
$(CC) $(CPPFLAGS) $(CFLAGS) -c
从.cc .cpp .C 到 .o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c
由.o 文件链接到可执行文件
$(CC) $(LDFLAGS) *.o $(LOADLIBES) $(LDLIBS)
阻止某些隐式规则:
- %.o : %.c
- %.o : %.cpp
- %.o : %exe
- 上述规则都不写方法,就可以阻止某些隐式规则
隐式规则中常见一些变量:
这些变量都有默认值,也可以自行修改
- CC
编译C语言的程序,默认为cc- CXX
编译C++的程序,默认为 g++- AR
归档程序,默认为 ar- CPP
C语言预处理程序,默认为$(CC)-E- RM
删除文件的程序,默认为rm -f- CFLAGS
传递给C编译器的一些选项,如-02 -linclude- CXXFLAGS
传递给C++编译器的一些选项,如-std=c++11-fexec-charset=GBK- CPPFLAGS
C语言预处理的一些选项- LDFLAGS
链接选项,如-L- LDLIBS
链接需要用到的库,如-kernel32 -luser32 -lgdi32
16、在Makefile中包含其他Makefile文件
包含其他makefile文件:
使用include指令可以读入其他makefile文件的内容,效果就如同在include的位置用对应的文件内容替换一样。例如:include mkf1 mkf2
include *.mk注意:包含其他Makefile文件可能会改变终极目标。