make官方文档
环境:VMware Workstation 17.x pro
下Ubuntu-24.04-live-sever-amd64
参考资料:
bilibili-无限三十年- 从零开始学Makefile(强烈推荐!!!)
关键词 | |
---|---|
.DEFAULT_GOAL = | 指定默认目标 |
.PHONY: | 标明伪目标 |
SHELL= | 手动指定Shell |
.ONESHELL: | 单进程执行shell指令 |
.SILENT= | 静默 |
define varName define varName:= define varName::= | 定义多行变量,默认= |
endef | 结束定义 |
undefine | 清除变量 |
$(var:.a=.b) $(var:%.a=%.b) | 变量替换引用 |
override | 抗覆盖 |
.SECONDEXPANSION: | 依赖中的变量延迟展开 |
VPATH = <dir1>:<dir2>... vpath <pattern> <dir1>:<dir2>... | 指定依赖文件搜索路径 |
ifdef var | 如果定义了 |
else | 否则 |
endif | 结束判断 |
ifndef var | 如果没定义 |
ifeq ($(var1),$(var2)) ifeq "$(var1)" "$(var2)" ifeq '$(var1)' "$(var2)" | 如果相等 |
ifneq ($(var1),$(var2)) ifneq "$(var1)" "$(var2)" ifneq '$(var1)' "$(var2)" | 如果不相等 |
运算符 | |
---|---|
@ | 静默 |
- | 忽视错误 |
$(var) ${var} | 变量引用 |
= | 延迟展开赋值 |
:= ::= | 立即展开赋值 |
?= | 条件赋值 |
+= | 追加 |
!= | shell运行赋值 |
&: | 组合多目标 |
make [target] [-f MakeFileName | --file=MakeFileName] [[var=value]]
命令行参数 | |
---|---|
target | 指定目标 |
-f --file= | 指定要make的makefile文件名 |
var= | 变量覆盖 |
搭建学习环境
apt-get install gcc g++ make
make --version
>> GNU Make 4.3
预备知识
make
使用流程
- 准备好需要编译的源代码
- 编写
Makefile
文件 - 终端中执行
make
指令
最简单的Makefile
vim main.cpp
//main.cpp
#include<iostram>
int main()
{
std::cout<<"Hello World!"<<std::endl;
return 0;
}
vim Makefile
#Makefile
hello:main.cpp
g++ main.cpp -o hello #缩进必须为一个Tab,不能为空格
make
>> g++ main.cpp -o hello
ls
>> Makefile hello main.cpp
./hello
>> Hello World!
通常将编译与链接分开写:
#Makefile
hello:main.o #递归形式
g++ main.o -o hello
main.o:main.cpp
g++ -c main.cpp
make
>> g++ -c main.cpp
>> g++ main.o -o hello
./hello
>> Hello World!
一个Makefile
文件由一条一条的规则构成,一条规则结构有如下两种写法:
target(目标): prerequisites(依赖)
recipe(更新方法)
target(目标): prerequisites(依赖); recipe(更新方法);
make
主要用于处理C
和C++
的编译工作,但不只能处理C
和C++
,所有能在终端运行的编译器/解释器指令都可以处理(例如Java
、Python
、 Golang
…)。所有基于一些文件(依赖)的改变去更新另一些文件(目标)的工作也可以用make
做。
Makefile文件的命名与指定
make
指令会自动查找makefile
文件,查找优先级为GNUmakefile
> makefile
> Makefile
-
GNUmakefile
:只能由GNU make
识别,其他版本的make
(如BSD make
,Windows nmake
等)不会识别。 -
Makefile
:最常使用,强烈建议。 -
如果运行
make
时没有找到以上名字的文件则会报错,这时候可以手动指定文件名:make -f mkfile make --file=Mkfile
Makefile
文件的内容组成
一个Makefile文件通常由五种类型的内容组成:显式规则、隐式规则、变量定义、指令和注释
- 显式规则(explicit rules):显式指明何时以及如何生成或更新目标文件,显式规则包括目标、依赖和更新方法三个部分。
- 隐式规则(implicit rules):根据文件自动推导如何从依赖生成或更新目标文件。
- 变量定义(variable definitions):定义变量并指定值,值都是字符串,类似C语言中的宏定义(
#define
),在使用时将值展开到引用位置。 - 指令(directives):在make读取Makefile的过程中做一些特别的操作,包括:
- 读取(包含)另一个makefile文件(类似C语言中的
#include
) - 确定是否使用或略过makefile文件中的一部分内容(类似C语言中的
#if
) - 定义多行变量
- 读取(包含)另一个makefile文件(类似C语言中的
- 注释(comments):一行当中
#
后面的内容都是注释,不会被make
执行。make
当中只有单行注释。如果需要用到#
而不是注释,则用\#
。
一个稍微复杂的Makefile
sudoku: block.o command.o input.o main.o scene.o test.o;g++ -o block.o command.o input.o main.o scene.o test.o;
block.o: block.cpp common.h block.h color.h;g++ -c block.cpp;
command.o: command.cpp scene.h common.h block.h command.h;g++ -c command.cpp;
input.o: input.cpp common.h utility.inl;g++ -c input.cpp;
main.o: main.cpp scene.h common.h block.h command.h input.h test.h;g++ -c main.cpp;
scene.o: scene.cpp common.h scene.h block.h command.h utility.inl; g++ -c scene.cpp;
test.o: test.cpp test.h scene.h common.h block.h command.h;g++ -c test.cpp;
clean:
rm block.o command.o input.o main.o scene.o test.o
make
> g++ -c block.cpp;
> g++ -c command.cpp;
> g++ -c input.cpp;
> g++ -c main.cpp;
> g++ -c scene.cpp;
> g++ -c test.cpp;
> g++ -o block.o command.o input.o main.o scene.o test.o;
#然后报错:undefined reference to `CBlock::CBlock()'
g++ -MM block.cpp
:获取block.cpp
所有依赖。- 不写
common.h block.h color.h
依赖虽然不会报错,但当改变这些文件时,make
不会重新编译。(不只是头文件)
基础知识
目标
- 通常来说目标是一个文件,一条规则的目的就是生成或更新目标文件。如果一个目标并不是一个文件,则这个目标称为伪目标(
clean
)。 make
会根据目标文件和依赖文件最后修改时间判断是否需要执行更新目标文件的方法。如果目标文件不存在或者目标文件最后修改时间早于其中一个依赖文件最后修改时间,则重新执行更新目标文件的方法。否则不会执行。Makefile
中会有很多目标,但最终目标只有一个,其他所有内容都是为这个最终目标服务的,写Makefile
的时候先写出最终目标,再依次解决总目标的依赖。- 一般情况第一条规则中的目标会被确立为最终目标,第一条规则默认会被
make
执行。 - 可以使用
.DEFAULT_GOAL
来修改默认最终目标。 - 除了最终目标对应的更新方法默认会执行外,如果
Makefile
中一个目标不是其他目标的依赖,那么这个目标对应的规则不会自动执行,需要手动指定。
all:
@echo all
main:
@echo main
make
> all
make main #指定执行的目标
> main
.DEFAULT_GOAL = main #指定默认目标
all:
@echo all
main:
@echo main
make
> main
make
时指定目标并不能确保目标真的有被执行,如果make
判定目标文件不需要更新时,或者在当前目录下有一个文件名称和伪目标同名了,那么目标就没法执行。如果需要每次不论有没有改动都执行某一目标的更新方法,或者为了确保执行某个伪目标,需要用到一个特殊的目标:.PHONY
。
.PHONY: clean test.o
test.o: test.cpp test.h
g++ -c test.cpp
clean:
rm block.o command.o input.o main.o scene.o test.o
依赖
类型
targets : normal-prerequisites | order-only-prerequisites
normal-prerequisites
为普通依赖(检查更新),order-only-prerequisites
为order-only
依赖(不检查更新),normal-prerequisites
部分可以为空。
指定搜索路径
VPATH = <dir1>:<dir2>...
vpath <pattern> <dir1>:<dir2>...
- 多个目录之间冒号隔开,这时
make
会在VPATH
指定的这些目录里面查找依赖文件。 vpath
比VPATH
更灵活,可以指定某个类型的文件在哪个目录搜索。
将所有.h
文件都放在include
文件夹中,将所有.cpp
文件都放在src
文件夹中。
VPATH=include:src
sudoku:block.cpp command.cpp
g++ $^ -Iinclude -o $@
vpath %.h include
vpath %.cpp src
sudoku:block.cpp command.cpp
g++ $^ -Iinclude -o $@
更新方法
执行终端
更新方法实际上是一些Shell
指令,都需要交给Shell
执行,所以需要符合Shell
语法。默认使用的Shell
是sh
,在Windows
上如果没有安装sh.exe
的话会自动查找使用cmd.exe
之类的终端,这时有的指令写法,例如循环语句,与Linux
不同,需要注意。
shell
进程
默认情况下,make
为每条指令启动一个新的shell实例。这意味着每个命令都在一个新的环境中执行,之前的命令设置的环境变量或改变的工作目录等都不会影响到后续的命令。而有时为了提高性能或其他原因,想让所有属于同一个目标的命令都会在一个独立的 shell
实例中连续执行,可以在Makefile
中添加.ONESHELL
。
a=10
echo $a
> 10
.PHONY:all
all:
a=10
@echo $$a
make
> a=10
>
.ONESHELL:
.PHONY:all
all:
a=10
@echo $$a
make
> a=10
> 10
- 若想在变量值中或在命令行中使用
$
符号,需要用两个$$
表示。
回显
Shell
语句会先打印指令,再打印执行内容,若不想打印指令可以在语句开头加@
,也可以使用.SILENT来指定哪些目标的更新方法指令不用打印。
错误处理
如果一条规则当中包含多条Shell
指令,每条指令执行完之后make都会检查返回状态,如果返回状态是0,则执行成功,继续执行下一条指令,直到最后一条指令执行完成之后,一条规则也就结束了。但如果过程中发生了错误,即某一条指令的返回值不是0,那么make
就会终止执行当前规则中剩下的Shell
指令。如果希望make
忽视错误继续下一条指令,在指令开头加-
可以达到这种效果。
SHELL = C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe #手动指定Shell
.ONESHELL: #连续执行shell指令
.SILENT= test.o #静默
pick:pick.o test.o
@echo connect
@g++ -o pick.o test.o
pick.o: pick.cpp pick.h
g++ -c pick.cpp
test.o: test.cpp test.h
g++ -c test.cpp
clean:
-rm pick.o test.o #忽视错误
-rm pick
变量
Makefile
中的变量有点类似C
语言中的宏定义,即用一个名称表示一串文本。但与C
语言宏定义不同的是,Makefile
的变量值是可以改变的。变量定义之后可以在目标、依赖、方法等Makefile文件的任意地方进行引用。
定义与引用
var1 = main.cpp color.h # <变量名>=变量值 or <变量名>:=变量值 or <变量名>::=变量值
var2 := main.o hello.o
var3 ::= $$a$$b$$c$$
dst: ${var2} # $(<变量名>) or ${<变量名>}
a=10 #a为shell变量,打印方式只能通过shell命令 echo $a
$(info ${var3}) #$(info $(var))用于打印makefile变量
g++ -o $(var2)
make
>g++ -c -o main.o main.cpp
>g++ -c -o hello.o hello.cpp
> $a$b$c$
- 变量名区分大小写,可以是任意字符串,不能含有
:
、#
、=
。 - 如果变量名只有一个字符,使用时可以不用括号,如
$a
, 但不建议这样使用,不管是否只有一个字符都应写成$(a)
或${a}
这种形式。
Makefile
执行过程
GNU make
分两个阶段来执行Makefile
,第一阶段(读取阶段):1. 读取Makefile
文件的所有内容。2. 根据Makefile
的内容在程序内建立起变量。3. 在程序内构建起显式规则、隐式规则。4. 建立目标和依赖之间的依赖图。第二阶段(目标更新阶段):1. 用第一阶段构建起来的数据确定哪些目标需要更新然后执行对应的更新方法。
- 变量和函数的展开如果发生在第一阶段,就称作立即展开,否则称为延迟展开。
- 显式规则中,目标和依赖中的变量都是立即展开。
变量赋值
递归展开赋值(延迟展开)
第一种方式就是直接使用=
,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量的时候再来进行处理得到变量值(Makefile
执行的第二个阶段再进行变量展开得到变量值)。
objs=block.cpp ommand.cpp
files=$(objs) #延迟展开
test=$(bar) #不存在变量,空串
all:
@echo $(files)
@echo $(test)
objs=block.cpp ommand.cpp test.o
make
>block.cpp ommand.cpp test.o
>
简单赋值(立即展开)
简单赋值使用:=
或::=
,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile
执行第一阶段进行变量展开)
objs=block.cpp ommand.cpp
files:=$(objs) #立即展开
all:
@echo $(files)
objs=block.cpp ommand.cpp test.o
make
>block.cpp ommand.cpp
条件赋值
条件赋值使用?=
,如果变量已经定义过了那么就保持原来的值,如果变量还没赋值过,就把右边的值赋给变量。
var1 = 100
var1 ?= 200
all:
@echo $(var1)
make
>100
追加
使用+=
在变量已有的基础上追加内容。
files = main.cpp
files += hello.cpp
all:
@echo $(files)
make
>main.cpp hello.cpp
Shell
运行赋值
使用!=
,将Shell
指令执行结果赋给变量。
gcc_version != gcc --version
all:
$(info $(gcc_version))
make
>gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0 Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
定义多行变量
<varable_name> = block.cpp \
test.cpp #本质仍是单行变量
define <varable_name> #默认为 =
# 变量内容
endef
define <varable_name> :=
# 变量内容
endef
define <varable_name> +=
# 变量内容
endef
清除变量
undefine <变量名>
环境变量的使用
all:
@echo $(USERNAME)
@echo $(JAVA_HOME)
@echo $(SystemRoot)
- 系统中的环境变量可以在
Makefile
中当变量一样直接使用。 - 终端中可使用
set
查看所有环境变量。
变量替换引用
files = main.cpp hello.cpp
objs := $(files:.cpp=.o) # main.o hello.o
# 另一种写法
objs := $(files:%.cpp=%.o)
files != ls *.cpp
objs=$(files:.cpp=.o)
变量覆盖
NormalVar := NormalVar
override OverridVar := OverridVar
all:
@echo $(NormalVar)
@echo $(OverridVar)
make
>NormalVar
>OverridVar
make NormalVar=hahaha #等号两边不能有空格
>hahaha
>OverridVar
make NormalVar="ha ha ha"
>ha ha ha
>OverridVar
make OverridVar=hahaha
>NormalVar
>OverridVar
- 所有在
Makefile
中的变量,都可以在执行make
时能过指定参数的方式进行覆盖。 - 如果变量值中有空格,需要用引号。
override
具有较高优先级,不会被命令参数覆盖。
绑定目标变量
global_var = hello world!
.PHONY:all
all: first.c second.o third.o
first.c:target_var1=hello,first!
%.o:target_var2=hello,o!
first.c:
@echo first.c global_var=$(global_var)
@echo first.c target_var1=$(target_var1)
@echo first.c target_var2=$(target_var2)
second.o:
@echo second.o global_var=$(global_var)
@echo second.o target_var1=$(target_var1)
@echo second.o target_var2=$(target_var2)
third.o:
@echo third.o global_var=$(global_var)
@echo third.o target_var1=$(target_var1)
@echo third.o target_var2=$(target_var2)
make
>first.c global_var=hello world!
>first.c target_var1=hello,first!
>first.c target_var2=
>second.o global_var=hello world!
>second.o target_var1=
>second.o target_var2=hello,o!
>third.o global_var=hello world!
>third.o target_var1=
>third.o target_var2=hello,o!
自动变量
应用 | |
---|---|
$@ | ①本条规则的目标名; ②如果目标是归档文件的成员,则为归档文件名; ③在多目标的模式规则中, 为导致本条规则方法执行的那个目标名; |
$< | 本条规则的第一个依赖名称 |
$? | 依赖中修改时间晚于目标文件修改时间的所有文件名,以空格隔开(获取需要更新的文件列表) |
$^ | 所有依赖文件名,文件名不会重复,不包含order-only 依赖 |
$+ | 所有依赖文件名,包括重复的文件名,不包含order-only 依赖 |
`$ | ` |
$* | 目标文件名的主干部分(即不包括后缀名) |
$% | 如果目标不是归档文件,则为空;如果目标是归档文件成员,则为对应的成员文件名 |
后缀D | 对应变量所在的目录,结尾不带/ |
后缀F | 对应变量除去目录部分的文件名 |
g++_c = g++ -c $<
files != ls *.cpp
objs=$(files:.cpp=.o)
/opt/sudoku/sudoku: $(objs)
@echo $@ #/opt/sudoku/sudoku
@echo $(@D) #/opt/sudoku
@echo $(@F) #sudoku
g++ -o $^
block.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 test.h
$(g++_c)
scene.o: scene.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)
clean:
@echo $@ #clean
-rm $(objs)
-rm /opt/sudoku/sudoku
二次展开
files = block.cpp command.cpp
.PHONY:sudoku
sudoku:$(files)
@echo $^
files=block.o command.o
make
>block.cpp command.cpp
files = block.cpp command.cpp
.PHONY:sudoku
.SECONDEXPANSION:
sudoku:$$(files)
@echo $^
files=block.o command.o
make
>g++ -c -o block.o block.cpp
>g++ -c -o command.o command.cpp
>block.o command.o
多目标与多规则
独立多目标
.PHONY: all main
#同依赖
all main:t1 t2 t3
#同依赖同方法
t1 t2 t3:
@echo $@ recipe executing ...
make
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...
make main
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...
files != ls *.cpp
objs=$(files:.cpp=.o)
/opt/sudoku/sudoku: $(objs)
g++ -o $^ $@
$(objs):
g++ -c $(@:%.o=%.cpp)
clean:
-rm $(objs)
-rm /opt/sudoku/sudoku
- 独立多目标虽然写在一起,但是每个目标都是单独调用一次方法来更新的,和分开写效果一样。
组合多目标
block.o command.o &: block.cpp common.h block.h color.h command.cpp scene.h common.h block.h command.h
g++ -c block.cpp
g++ -c command.cpp
- 组合多目标只调用一次方法将更新所有目标,因此所有目标的更新指令都得写在方法中。
多规则
all: t1 t2
@echo all-1
@echo $^
all: t3 t4
@echo all-2
@echo $^
t1 t2 t3 t4:
@echo $@ recipe executing ...
make
>t3 recipe executing ...
>t4 recipe executing ...
>t1 recipe executing ...
>t2 recipe executing ...
>all-2
>t3 t4 t1 t2
all: t1 t2
@echo all-1
@echo $^
all: t3 t4
t1 t2 t3 t4:
@echo $@ recipe executing ...
make
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...
>t4 recipe executing ...
>all-1
>t1 t2 t3 t4
- 同一目标可以对应多条规则,规则中的依赖会被合并,新方法将会覆盖旧方法。
静态模式
独立多目标可以简化
Makefile
的书写,但是不利于将各个目标的依赖分开,让目标文件根据各自的依赖进行更新。静态模式可以在一定程度上改进依赖分开问题。
targets(目标) : target-pattern(目标模式): prereq-patterns(依赖模式)
recipe(方法)
objs= block.o command.o input.o scene.o test.o
sudoku:$(objs) main.o
g++ -o $^
$(objs): %.o : %.cpp %.h
g++ -c $<
main.o:main.cpp
g++ -c $<
clean:
@echo $@ #clean
-rm $(objs)
-rm /opt/sudoku/sudoku
条件判断
ifdef | ifndef
Linux=Ubuntu64
.PHONY=all
all:
$(info $(OS))
ifdef Win
OS = $(Windows) #不可缩进
else ifdef Mac
OS= $(MacOS)
else
OS = $(Linux)
endif
make
>Ubuntu64
ifeq | ifneq
version = 3.0
.PHONY=all
all:
$(info $(version))
ifeq (version,1.0) #一定要留一个空格
@echo 版本太低,请立即更新版本 #必须缩进
else ifeq '$(version)' '3.0'
@echo 最新版本
else
@echo 版本可用
endif
make
> 3.0
> 最新版本
Makefile
的语法要求命令行必须以制表符(Tab
)开头,而变量定义和其他非命令行内容则不应该缩进。
函数
C
语言中,函数调用方法是function(arguments);
但在Makefile
中调用函数的写法是:
$(function arguments) 或 ${function arguments}
$(function arg1,$(arg2),arg3 ...) # 参数之间不可有空格
字符替换与分析 | |
---|---|
$(subst str1,str2,text) | 用str2 替换text 中的str1 ,返回替换后的文本 |
$(strip text) | 去除text 头部和尾部的空格,中间如果连续有多个空格,则用一个空格替换,返回去除空格后的文本。 |
$(findstring substr,text) | 在text 中查找substr ,如果找到了则返回substr ,如果没找到则反回空串。 |
$(filter pattern,text) | 从文本中筛选出符合模式的内容并返回。 |
$(filter-out pattern,text) | 过滤掉符合模式的,返回剩下的内容。 |
$(sort list) | 将文本内的各项按字典顺序排列,并且移除重复项 |
$(word n,text) | 用于返回文本中第n 个单词,单词以空格分割。如果n 大于总单词数,则返回空串。 |
$(wordlist start,end,text) | 用于返回text 指定范围内的单词列表。 |
$(words text) | 返回text 中单词数 |
$(firstword text) | 返回text 中第一个单词 |
$(lastword text) | 返回text 中最后一个单词 |
文件名处理 | |
$(dir files) | 返回文件目录 |
$(notdir files) | 返回除目录部分的文件名 |
$(suffix files) | 返回文件后缀名,如果没有后缀返回空 |
$(basename files) | 返回文件名除后缀的部分 |
$(addsuffix suffix,files) | 给文件名添加后缀。 |
$(addprefix prefix,files) | 给文件名添加前缀 |
$(join list1,list2) | 将两个列表中的内容一对一连接,如果两个列表内容数量不相等,则多出来的部分原样返回。 |
$(wildcard pattern) | 返回符合通配符的文件列表 |
$(realpath files) | 返回文件的绝对路径,若文件不存在则返回空。 |
$(abspath files) | 返回文件的绝对路径,若文件不存在则返回当前文件夹下的绝对路径。 |
条件 | |
$(if condition,then-part[,else-part]) | — condition 条件部分— then-part 条件为真时执行的部分— else-part 条件为假时执行的部分,如果省略则为假时返回空串。 |
$(or condition1[,condition2[,condition3…]]) | 返回条件中第一个不为空的部分 |
$(and condition1[,condition2[,condition3…]]) | 如果条件中有一个为空串,则返回空,如果全都不为空,则返回最后一个条件。 |
$(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 。 |
信息提示 | |
$(error text) | 提示错误信息并终止make 执行 |
$(warning text) | 提示警告信息,make 不会终止 |
$(info text) | 输出一些信息 |
其他 | |
file | 读写文件 |
value | 对于不是立即展开的变量,可以查看变量的原始定义;对于立即展开的变量,直接返回变量值。 |
origin | 查看一个变量定义来源 |
flavor | 查看一个变量的赋值方式 |
shell | 用于执行Shell 命令 |
foreach | 对一列用空格隔开的字符序列中每一项进行处理,并返回处理后的列表 |
call | 将一些复杂的表达式写成一个变量,用call可以像调用函数一样进行调用。 |
let | 将一个字符串序列中的项拆开放入多个变量中,并对各个变量进行操作(GNU make 4.4 以上版本) |
eval | 在 Make 运行时动态评估和执行 Makefile 代码。 |
file
$(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)
value
$(value variable)
var = value function test
var2 = $(var)
var3 := $(var)
all:
@echo $(value var2) #$(var)
@echo $(value var3) #value function test
origin
$(origin variable)
var2 = origin function
all:
@echo $(origin var1) # undefined 未定义
@echo $(origin CC) # default 默认变量
@echo $(origin JAVA_HOME) # environment 环境变量
@echo $(origin var2) # file 在Makefile文件中定义的变量
@echo $(origin @) # automatic 自动变量
flavor
$(flavor variable)
var2 = flavor function
var3 := flavor funciton
all:
@echo $(flavor var1) # undefined 未定义
@echo $(flavor var2) # recursive 递归展开赋值
@echo $(flavor var3) # simple 简单赋值
shell
files = $(shell ls *.cpp)
$(shell echo This is from shell function)
foreach
$(foreach each,list,process)
--- each list中的每一项
--- list 需要处理的字符串序列,用空格隔开
--- process 需要对每一项进行的处理
list = 1 2 3 4 5
result = $(foreach each,$(list),$(addprefix cpp,$(addsuffix .cpp,$(each))))
call
$(call funcname,param1,param2,…)
--- funcname 自定义函数(变量名)
--- 参数至少一个,可以有多个,用逗号隔开
dirof = $(dir $(realpath $(1))) $(dir $(realpath $(2)))
result = $(call dirof,main.cpp,src/hello.cpp)
- 类似于编程语言中的自定义函数。在函数中可以用$(n)来访问第n个参数
let
$(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)) #a
letrest = $(let first second rest,$(list),$(rest)) #cd
# 结合call可以对所有项进行递归处理
reverse = $(let first rest,$(1),$(if $(rest),$(call reverse,$(rest)) )$(first))
all: ; @echo $(call reverse,d c b a) #a b c d
eval
$(eval expression)
---expression为要评估和执行的 Makefile 代码。
# 定义源文件列表
SOURCES = file1.c file2.c file3.c
# 定义目标文件列表
OBJECTS = $(SOURCES:.c=.o)
# 定义编译器和标志
CC = gcc
CFLAGS = -Wall -O2
#与call结合使用,类似自定义函数
define compile_rule
$(1): $(1:.o=.c)
$(CC) $(CFLAGS) -c $$< -o $$@
endef
# 遍历 OBJECTS 列表,生成每个目标文件的规则,并通过eval进行评估和执行
$(foreach obj,$(OBJECTS),$(eval $(call compile_rule,$(obj))))
# 默认目标
.PHONY: all
all: $(OBJECTS)
@echo "All object files are compiled: $(OBJECTS)"
扩展知识
隐式规则
Makefile
中有一些生成目标文件的规则使用频率非常高,比如由.c
或.cpp
文件编译成.o
文件,这样的规则在make
中可以自动推导,所以可以不用明确写出来,这样的规则称为隐式规则。
例子
C
语言编译
$(CC) $(CPPFLAGS) $(CFLAGS) -c #从.c到.o
C++
编译
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c #从.cc .cpp .C到.o
链接
$(CC) $(LDFLAGS) *.o $(LDLIBS) #由.o文件链接到可执行文件
补充
隐式规则,利用模式匹配可以直接将所有.cpp
到.o
文件的编译简写为如下:
%.o : %.cpp %.h
g++ -c $<
若只写目标和依赖,不写方法,则是阻止相关隐式规则:
%.o : %.cpp
常用变量
这些变量都有默认值,也可以自行修改
CC | 编译C 语言的程序,默认为 cc |
CXX | 编译C++ 的程序,默认为 g++ |
AR | 归档程序,默认为 ar |
CPP | C 语言预处理程序,默认为 $(CC) -E |
RM | 删除文件的程序,默认为rm -f |
CFLAGS | 传递给C 编译器的一些选项,如-O2 -Iinclude |
CXXFLAGS | 传递给C++ 编译器的一些选项,如-std=c++11 -fexec-charset=GBK |
CPPFLAGS | C 语言预处理的一些选项 |
LDFLAGS | 链接选项,如-L . |
LDLIBS | 链接需要用到的库,如-lkernel32 -luser32 -lgdi32 |
objs=main.o bolck.o command.o
vpath %.a src
CC=g++
CXXFLAGS+=-Iinclude
CXXFLAGS+=-fexec-charset=GBK -finput-charset=UTF-8
LDFLAGS+= -Lsrc
LDLIBS+=-lsuduku
main:$(objs)
.PHONY:clean
clean:
-$(RM) *.exe *.o
多Makefile
包含其他makefile
文件
使用include
指令可以读入其他makefile
文件的内容,效果就如同在include
的位置用对应的文件内容替换一样。
include mkf1 mkf2 # 可以引入多个文件,用空格隔开
include *.mk # 可以用通配符,表示引入所有以.mk结尾的文件
如果找不到对应文件,则会报错,如果要忽略错误,可以在include
前加-
-include mkf1 mkf2
应用实例:自动生成依赖
objs = block.o command.o input.o main.o scene.o test.o
sudoku: $(objs)
g++ $(objs) -o sudoku
include $(objs:%.o=%.d)
%.d: %.cpp
@-rm $@
$(CXX) -MM $< > $@.temp
@sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.temp > $@
@-rm $@.temp
%.o : %.cpp
g++ -c $<
@echo $^
嵌套make
如果将一个大项目分为许多小项目,则可以使用嵌套(递归)使用make
。具体做法为,写一个总的Makefile
,然后在每个子项目中都写一个Makefile
,在总Makefile
中进行调用。
例如,可以把sudoku
项目中除main.cpp
,test.cpp
外的其他cpp
存为一个子项目,编译为一个库文件,main.cpp
test.cpp
为另一个子项目,编译为.o
然后链接库文件成可执行文件:
库文件Makefile
vpath %.h ../include
CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
cpps := $(wildcard *.cpp)
objs := $(cpps:%.cpp=%.o)
libsudoku.a: $(objs)
ar rcs $@ $^
$(objs): %.o : %.cpp %.h
main.cpp
test.cpp
的Makefile
CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
vpath %.h ../include
vpath %.a ../lib
../sudoku: main.o test.o -lsudoku
$(CXX) -o $@ $^
总的Makefile
.PHONY: all clean
all: subsrc
subsrc: sublib
$(MAKE) -C src
sublib:
$(MAKE) -C lib
clean:
-rm *.exe src/*.o lib/*.o lib/*.a
其中
$(MAKE) -C subdir
这一指令会自动进入subdir文件夹然后执行make
。
可以通过export
指令向子项目的make
传递变量。
export var # 传递var
export # 传递所有变量
unexport # 取消传递