Makefile用法
Makefile用法
Beyond学习环境搭建
Linux(以Ubuntu为例)
1 | sudo apt install gcc g++ make |
Windows
学习与演示过程以Windows为主,Windows上装MinGW环境,MinGW官网: https://www.mingw-w64.org/ 之前我们提过两个版本的环境,****MingW-W64-builds和w64devkit 推荐使用****w64devkit套件,里面工具比较齐全,还提供模拟了许多Linux命令,用这个套件环境来学习可以保持在Linux与Windows上Makefile书写方式一致。** **以下是w64devkit与其他包一些命令的区别
w64devkit(模拟Linux) | MingW-W64-builds或其他套件(Windows cmd命令) |
---|---|
make | mingw32-make |
cc | gcc |
rm | del |
touch | |
ls | dir |
sh | |
mv | |
cp | copy/xcopy |
sed |
Makefile基础知识
make使用流程
- 准备好需要编译的源代码
- 编写Makefile文件
- 在命令行执行make命令
最简单的Makefile
1 | hello: hello.cpp |
但通常需要将编译与链接分开写,分为如下两步
1 | hello: hello.o |
规则(Rules):一个Makefile文件由一条一条的规则构成,一条规则结构如下
1 | target … (目标): prerequisites …(依赖) |
第二种写法
1 | target … (目标): prerequisites …(依赖); recipe(方法) ;… |
** ** ** ** Make主要用于处理C和C++的编译工作,但不只能处理C和C++,所有编译器/解释器能在命令行终端运行的编程语言都可以处理(例如Java、Python、 Golang….)。Make也不只能用来处理编程语言,所有基于一些文件(依赖)的改变去更新另一些文件(目标)的工作都可以做。
Make编译与打包Java程序示例
1 | snake.jar : C.class Main.class SnakeFrame.class SnakePanel.class |
Makefile文件的命名与指定
Make会自动查找makefile文件,查找顺序为GNUmakefile -> makefile -> Makefile
GNUmakefile:不建议使用,因为只有GNU make会识别,其他版本的make(如BSD make, Windows nmake等)不会识别,如果只给GNU make使用的情况
makefile:可以使用,GNU make和其他版本make识别
Makefile:最常用,强烈建议使用
如果运行make的时候没有找到以上名字的文件,则会报错,这时候可以手动指定文件名
1 | make -f mkfile # make -f <filename> |
手动指定之后,make就会使用指定的文件,即使有Makefile或者makefile不会再自动使用
Makefile文件内容组成
一个Makefile文件通常由五种类型的内容组成:显式规则、隐式规则、变量定义、指令和注释
显式规则(explicit rules):显式指明何时以及如何生成或更新目标文件,显式规则包括目标、依赖和更新方法三个部分
隐式规则(implicit rules):根据文件自动推导如何从依赖生成或更新目标文件。
变量定义(variable definitions):定议变量并指定值,值都是字符串,类似C语言中的宏定义(#define),在使用时将值展开到引用位置
指令(directives):在make读取Makefile的过程中做一些特别的操作,包括:
- 读取(包含)另一个makefile文件(类似C语言中的#include)
- 确定是否使用或略过makefile文件中的一部分内容(类似C语言中的#if)
- 定义多行变量
注释(comments):一行当中 # 后面的内容都是注释,不会被make执行。make当中只有单行注释。如果需要用到#而不是注释,用\#。
一个稍微复杂的Makefile
1 | sudoku: block.o command.o input.o main.o scene.o test.o |
1 | target … (目标): prerequisites …(依赖) |
目标
- Makefile中会有很多目标,但最终目标只有一个,其他所有内容都是为这个最终目标服务的,写Makefile的时候****先写出最终目标,再依次解决总目标的依赖
- 一般情况第一条规则中的目标会被确立为最终目标,第一条规则默认会被make执行
- 通常来说目标是一个文件,一条规则的目的就是生成或更新目标文件。
- make会根据目标文件和依赖文件最后修改时间判断是否需要执行更新目标文件的方法。如果目标文件不存在或者目标文件最后修改时间早于其中一个依赖文件最后修改时间,则重新执行更新目标文件的方法。否则不会执行。
- 除了最终目标对应的更新方法默认会执行外,如果Makefile中一个目标不是其他目标的依赖,那么这个目标对应的规则不会自动执行。需要手动指定,方法为
1
make <target> # 如 make clean , make hello.o
- 可以使用.DEFAULT_GOAL来修改默认最终目标
1
2
3
4
5
6
7.DEFAULT_GOAL = main
all:
@echo all
main:
@echo main
伪目标
**如果一个标并不是一个文件,则这个目标就是伪目标。例如前面的clean目标。如果说在当前目录下有一个文件名称和这个目标名称冲突了,则这个目标就没法执行。这时候需要用到一个特殊的目标 .PHONY,将上面的clean目标改写如下 **
1 | .PHONY: clean |
这样即使当前目录下存在与目标同名的文件,该目标也能正常执行。
伪目标的其他应用方式
如果一条规则的依赖文件没有改动,则不会执行对应的更新方法。如果需要每次不论有没有改动都执行某一目标的更新方法,可以把对应的目标添加到.PHONY的依赖中,例如下面这种方式,则每次执行make都会更新test.o,不管其依赖文件有没有改动
1 | test.o: test.cpp test.h |
依赖
依赖类型
普通依赖
前面说过的这种形式都是普通依赖。直接列在目标后面。普通依赖有两个特点:
- **如果这一依赖是由其他规则生成的文件,那么执行到这一目标前会先执行生成依赖的那一规则 **
- 如果任何一个依赖文件修改时间比目标晚,那么就重新生成目标文件
order-only依赖
依赖文件不存在时,会执行对应的方法生成,但依赖文件更新并不会导致目标文件的更新
如果目标文件已存在,order-only依赖中的文件即使修改时间比目标文件晚,目标文件也不会更新。
定义方法如下:
1 | targets : normal-prerequisites | order-only-prerequisites |
normal-prerequisites部分可以为空
指定依赖搜索路径
make默认在Makefile文件所在的目录下查找依赖文件,如果找不到,就会报错。这时候就需要手动指定搜索路径,用VPATH变量或vpath指令。
VPATH用法如下:
1 | VPATH = <dir1>:<dir2>:<dir3>... |
多个目录之间冒号隔开,这时make会在VPATH指定的这些目录里面查找依赖文件。
vpath指令用法:
vpath比VPATH使用更灵活,可以指定某个类型的文件在哪个目录搜索。
用法如下:
1 | vpath <pattern> <directories> |
更新方法
1 | target … (目标): prerequisites …(依赖) |
关于执行终端
更新方法实际上是一些Shell指令,通常以Tab开头,或直接放在目标-依赖列表后面,用分号隔开。这些指令都需要交给Shell执行,所以需要符合Shell语法。默认使用的Shell是sh,在Windows上如果没有安装sh.exe的话会自动查找使用cmd.exe之类的终端。这时有的指令写法,例如循环语句,与Linux不同,需要注意。
可以通过SHELL变量手动指定Shell
1 | SHELL = C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe |
默认的执行方式为一条指令重新调用一个Shell进程来执行。有时为了提高性能或其他原因,想让这个目标的所有指令都在同一进程中执行,可以在Makefile中添加 .ONESHELL
1 | .ONESHELL: |
这样所有指令都会在同一次Shell调用中执行
Shell语句回显问题
通常make在执行一条Shell语句前都会先打印这条语句,如果不想打印可以在语句开头在@
1 | @echo hello |
也可以使用.SILENT来指定哪些目标的更新方法指令不用打印
1 | .SILENT: main all |
错误处理
如果一条规则当中包含多条Shell指令,每条指令执行完之后make都会检查返回状态,如果返回状态是0,则执行成功,继续执行下一条指令,直到最后一条指令执行完成之后,一条规则也就结束了。
如果过程中发生了错误,即某一条指令的返回值不是0,那么make就会终止执行当前规则中剩下的Shell指令。
例如
1 | clean: |
这时如果第一条rm main.o hello.o出错,第二条rm main.exe就不会执行。类似情况下,希望make忽视错误继续下一条指令。在指令开头-
可以达到这种效果。
1 | clean: |
变量应用
Makefile中的变量有点类似C语言中的宏定义,即用一个名称表示一串文本。但与C语言宏定义不同的是,Makefile的变量值是可以改变的。变量定义之后可以在目标、依赖、方法等Makefile文件的任意地方进行引用。
Makefile中的变量值只有一种类型: 字符串
变量可以用来表示什么
- 文件名序列
- 编译选项
- 需要运行的程序
- 需要进行操作的路径
- ……
变量定义与引用方式
定义方式
1 | # <变量名> = <变量值> <变量名> := <变量值> <变量名> ::= <变量值> |
变量名区分大小写,可以是任意字符串,不能含有”:”, “#”, “=”
使用方式
1 | # $(<变量名>) 或者 ${<变量名>} |
如果变量名只有一个字符,使用时可以不用括号,如$a, $b, 但不建议这样用,不管是否只有一个字符都写成$(a), $(b)这种形式
Makefile读取过程
GNU make分两个阶段来执行Makefile,第一阶段(读取阶段):
- 读取Makefile文件的所有内容
- 根据Makefile的内容在程序内建立起变量
- 在程序内构建起显式规则、隐式规则
- 建立目标和依赖之间的依赖图
第二阶段(目标更新阶段):
- 用第一阶段构建起来的数据确定哪个目标需要更新然后执行对应的更新方法
变量和函数的展开如果发生在第一阶段,就称作****立即展开,否则称为延迟展开。立即展开的变量或函数在第一个阶段,也就是Makefile被读取解析的时候就进行展开。延迟展开的变量或函数将会到用到的时候才会进行展开,有以下两种情况:
- 在一个立即展开的表达式中用到
- 在第二个阶段中用到
显式规则中,目标和依赖部分都是立即展开,在更新方法中延迟展开
变量赋值
递归展开赋值(延迟展开)
第一种方式就是直接使用=,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量的时候再来进行处理得到变量值(Makefile执行的第二个阶段再进行变量展开得到变量值)
1 | bar2 = ThisIsBar2No.1 |
简单赋值(立即展开)
简单赋值使用:**=或::=**,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)
1 | bar2 := ThisIsBar2No.1 |
条件赋值
条件赋值使用?=,如果变量已经定义过了(即已经有值了),那么就保持原来的值,如果变量还没赋值过,就把右边的值赋给变量。
1 | var1 = 100 |
练习:试求a的值
1 | x = hello |
追加
使用+=在变量已有的基础上追加内容
1 | files = main.cpp |
Shell运行赋值
使用!=,运行一个Shell指令后将返回值赋给一个变量
1 | gcc_version != gcc --version |
如果使用Windows需要注意,这种赋值方式只适用于与Linux相同的Shell指令,Windows独有的指令不能这样使用。
定义多行变量
前面定义的变量都是单行的。
变量值有多行,多用于定义shell指令
语法
1 | define <varable_name> # 默认为 = |
示例
1 | echosomething = @echo This is the first line |
取消变量
如果想清除一个变量,用以下方法
1 | undefine <变量名> 如 undefine files, undefine objs |
环境变量的使用
系统中的环境变量可以直接在Makefile中直接使用,使用方法跟普通变量一样
1 | all: |
变量替换引用
语法:****$(var:a=b)****,意思是将变量var的值当中每一项结尾的a替换为b,直接上例子
1 | files = main.cpp hello.cpp |
变量覆盖
所有在Makefile中的变量,都可以在执行make时能过指定参数的方式进行覆盖。
1 | OverridDemo := ThisIsInMakefile |
如果直接执行
1 | make |
则上面的输出内容为ThisIsInMakefile,但可以在执行make时指定参数:
1 | make OverridDemo=ThisIsFromOutShell # 等号两边不能有空格 |
则输出OverridDemo的值是ThisIsFromOutShell或This Is From Out Shell。
用这样的命令参数会覆盖Makefile中对应变量的值,如果不想被覆盖,可以在变量前加上override指令,override具有较高优先级,不会被命令参数覆盖
1 | override OverridDemo := ThisIsInMakefile |
这样即使命令行指定参数
1 | make OverridDemo=ThisIsFromOutShell |
输出结果依然是ThisIsInMakefile
自动变量
$@****:①本条规则的目标名;②如果目标是归档文件的成员,则为归档文件名;③在多目标的模式规则中, 为导致本条规则方法执行的那个目标名;
$<****:本条规则的第一个依赖名称
$?****:依赖中修改时间晚于目标文件修改时间的所有文件名,以空格隔开
$^****:所有依赖文件名,文件名不会重复,不包含order-only依赖
$+****:类似上一个, 表示所有依赖文件名,包括重复的文件名,不包含order-only依赖
$|****:所有order-only依赖文件名
$*****:(简单理解)目标文件名的主干部分(即不包括后缀名)
$%****:如果目标不是归档文件,则为空;如果目标是归档文件成员,则为对应的成员文件名
以下变量对应上述变量,D为对应变量所在的目录,结尾不带/,F为对应变量除去目录部分的文件名
$(@D)****
$(@F)****
$(*D)****
$(*F)****
$(%D)****
$(%F)****
$(<D)****
$(<F)****
$(^D)****
$(^F)****
$(+D)****
$(+F)****
$(?D)****
$(?F)****
绑定目标的变量
Makefile中的变量一般是全局变量。也就是说定义之后在Makefile的任意位置都可以使用。但也可以将变量指定在某个目标的范围内,这样这个变量就只能在这个目标对应的规则里面保用
语法
1 | target … : variable-assignment |
例
1 | var1 = Global Var |
这种定义变量的方式,目标也可以使用模式匹配,这样所有能匹配上的目标范围内都可以使用这些变量
1 | var1 = Global Var |
二次展开
前面说过依赖中的变量都是在Makefile读取阶段立即展开的。如果想让依赖的的变量延迟展开,可以使用.SECONDEXPANSION:,添加之后,在依赖中使用变量时用$$
,可以让变量在第二阶段进行二次展开,从而达到延迟展开的效果。
1 | VAR1 = main.cpp |
自动推导与隐式规则
Makefile中有一些生成目标文件的规则使用频率非常高,比如由.c或.cpp文件编译成.o文件,这样的规则在make中可以自动推导,所以可以不用明确写出来,这样的规则称为隐式规则。
一些make预定义的规则
C语言编译
从.c到.o
1 | $(CC) $(CPPFLAGS) $(CFLAGS) -c |
C++编译
从.cc .cpp .C到.o
1 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c |
链接
由.o文件链接到可执行文件
1 | $(CC) $(LDFLAGS) *.o $(LOADLIBES) $(LDLIBS) |
隐式规则中常用一些变量
这些变量都有默认值,也可以自行修改
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
多目标与多规则
显式规则中一条规则可以有多个目标,多个目标可以是相互独立的目标,也可以是组合目标,用写法来区分
独立多目标
相互独立的多个目标与依赖之间直接用:
,常用这种方式的有以下两种情况
只需要写目标和依赖,不需要写方法的时候
1
block.o input.o scene.o : common.h
这种写法等价于
1
2
3block.o : common.h
input.o : common.h
scene.o : common.h生成(更新)目标的方法写法一样的,只是依赖与目标不一样时。之前写的Makfile中,有如下代码:
1
2
3
4
5
6
7
8
9
10
11
12block.o: block.cpp common.h block.h color.h
g++ -c block.cpp
command.o: command.cpp command.h scene.h
g++ -c command.cpp
input.o: input.cpp common.h utility.inl
g++ -c input.cpp
main.o: main.cpp scene.h input.h test.h
g++ -c main.cpp
scene.o: scene.cpp common.h scene.h utility.inl
g++ -c scene.cpp
test.o: test.cpp test.h
g++ -c test.cpp所有.o文件的生成都用的同一方法
1
g++ -c <文件名>
如果不考虑依赖源文件进行更新时,可以进行简写如下:
1
2block.o command.o input.o main.o scene.o test.o : common.h block.h command.h ...
g++ -c $(@:%.o=%.cpp)这种写法实际上等价于
1
2
3
4
5
6
7
8
9
10
11
12block.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)
command.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)
input.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)
main.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)
scene.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)
test.o : common.h block.h command.h ...
g++ -c $(subst .o,.cpp,$@)其中,$@表示的是目标名称。subst是一个字符串替换函数,$(subst .o,.cpp,$@)表示将目标名称中的.o替换为.cpp。
这样的简写可以减少内容的书写量,但是不利于将每个目标与依赖分别对应。
独立多目标虽然写在一起,但是每个目标都是单独调用一次方法来更新的。和分开写效果一样。
组合多目标
多目标与依赖之前用&:
,这样的多个目标称为组合目标。与独立多目标的区别在于,独立多目标每个目标的更新需要单独调用一次更新方法。而组合多目标调用一次方法将更新所有目标
1 | block.o input.o scene.o &: block.cpp input.cpp scene.cpp common.h |
所有目标的更新方法都写到其中,每次更新只会调用一次。
同一目标多条规则
同一目标可以对应多条规则。同一目标的所有规则中的依赖会被合并。但如果同一目标对应的多条规则都写了更新方法,则会使用最新的一条更新方法,并且会输出警告信息。
同一目标多规则通常用来给多个目标添加依赖而不用改动已写好的部分。
1 | input.o: input.cpp utility.inl |
同时给三个目标添加了一个依赖common.h,但是不用修改上面已写好的部分。
静态模式
独立多目标可以简化Makefile的书写,但是不利于将各个目标的依赖分开,让目标文件根据各自的依赖进行更新。静态模式可以在一定程度上改进依赖分开问题。
静态模式就是用%
进行文件匹配来推导出对应的依赖。
语法
1 | targets …: target-pattern(目标模式): prereq-patterns(依赖模式) … |
先看一个例子
1 | block.o : %.o : %.cpp %.h |
block.o为目标,%.o为目标模式,%.cpp,%.h为依赖模式,对于这一条规则,%.o代表的是目标文件block.o,所以这里的%匹配的是block,因此,%.cpp表示block.cpp,%.h代表block.h,所以block.o : %.o : %.cpp %.h表示的意思同下面这种写法
1 | block.o : block.cpp block.h |
自动推导出block.o依赖block.cpp和block.h。
另外,$<表示目标的第一个依赖,在这条规则中,$<表示block.cpp
对应的Makefile可以做如下改进
1 | block.o command.o input.o scene.o test.o: %.o : %.cpp %.h |
用这种方式可以在简写的同时一定程度上解决各个目标对应的依赖问题。
(不属于静态模式的内容,隐式规则的内容)利用模式匹配可以直接将所有.cpp到.o文件的编译简写为如下
1 | %.o : %.cpp %.h |
条件判断
使用条件指令可以让make执行或略过Makefile文件中的一些部分。
ifdef 判断一个变量是已否定义
1 | OS = Linux |
ifndef 判断一个变量是否没被定义
1 | ifndef FLAGS |
ifeq 判断两个值是否相等
1 | version = 3.0 |
ifneq 判断两个值是否不等
用法及参数同ifeq,只是判断结果相反
文本处理函数
C语言中,函数调用方法是function(arguments);但在Makefile中调用函数的写法不同
1 | $(function arguments) 或 ${function arguments} |
字符替换与分析
subst
文本替换函数,返回替换后的文本
1 | $(subst target,replacement,text) |
patsubst
模式替换, 返回替换后的文本
1 | $(patsubst pattern,replacement,text) |
strip
去除字符串头部和尾部的空格,中间如果连续有多个空格,则用一个空格替换,返回去除空格后的文本
1 | $(strip string) |
findstring
查找字符串,如果找到了,则返回对应的字符串,如果没找到,则反回空串
1 | $(findstring find,string) |
filter
从文本中筛选出符合模式的内容并返回
1 | $(filter pattern…,text) |
filter-out
与filter相反,过滤掉符合模式的,返回剩下的内容
1 | $(filter-out pattern…,text) |
sort
将文本内的各项按字典顺序排列,并且移除重复项
1 | $(sort list) |
word
用于返回文本中第n个单词
1 | $(word n,text) |
wordlist
用于返回文本指定范围内的单词列表
1 | $(wordlist start,end,text) |
words
返回文本中单词数
1 | $(words text) |
firstword
返回第一个单词
1 | $(firstword text) |
lastword
返回最后一个单词
1 | $(lastword text) |
文件名处理函数
dir
返回文件目录
1 | $(dir files) |
notdir
返回除目录部分的文件名
1 | $(notdir files) |
suffix
返回文件后缀名,如果没有后缀返回空
1 | $(suffix files) |
basename
返回文件名除后缀的部分
1 | $(basename files) |
addsuffix
给文件名添加后缀
1 | $(addsuffix suffix,files) |
addprefix
给文件名添加前缀
1 | $(addprefix prefix,files) |
join
将两个列表中的内容一对一连接,如果两个列表内容数量不相等,则多出来的部分原样返回
1 | $(join list1,list2) |
wildcard
返回符合通配符的文件列表
1 | $(wildcard pattern) |
realpath
返回文件的绝对路径
1 | $(realpath files) |
abspath
返回绝对路径,用法同realpath,如果一个文件名不存在,realpath不会返回内容,abspath则会返回一个当前文件夹一下的绝对路径
1 | $(abspath files) |
条件函数
if
条伯判断,如果条件展开不是空串,则反回真的部分,否则返回假的部分
1 | $(if condition,then-part[,else-part]) |
or
返回条件中第一个不为空的部分
1 | $(or condition1[,condition2[,condition3…]]) |
and
如果条件中有一个为空串,则返回空,如果全都不为空,则返回最后一个条件
1 | $(and condition1[,condition2[,condition3…]]) |
intcmp
比较两个整数大小,并返回对应操作结果(GNU make 4.4以上版本)
1 | $(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]]) |
file
读写文件
1 | $(file op filename[,text]) |
foreach
对一列用空格隔开的字符序列中每一项进行处理,并返回处理后的列表
1 | $(foreach each,list,process) |
作用类似C/C++中的循环
1 | int list[5] = {1, 2, 3, 4, 5}; |
call
将一些复杂的表达式写成一个变量,用call可以像调用函数一样进行调用。类似于编程语言中的自定义函数。在函数中可以用$(n)来访问第n个参数
1 | $(call funcname,param1,param2,…) |
value
对于不是立即展开的变量,可以查看变量的原始定义;对于立即展开的变量,直接返回变量值
1 | $(value variable) |
**## **
查看一个变量定义来源
1 | $(origin variable) |
flavor
查看一个变量的赋值方式
1 | $(flavor variable) |
eval
可以将一段文本生成Makefile的内容
1 | $(eval text) |
以上,运行make时将会执行eval目标
shell
用于执行Shell命令
1 | files = $(shell ls *.cpp) |
let
将一个字符串序列中的项拆开放入多个变量中,并对各个变量进行操作(GNU make 4.4以上版本)
1 | $(let var1 [var2 ...],[list],proc) |
信息提示函数
error
提示错误信息并终止make执行
1 | $(error text) |
warning
提示警告信息,make不会终止
1 | $(warning text) |
info
输出一些信息
1 | $(info text…) |
同一项目中有多个Makefile文件
包含其他makefile文件
使用include
指令可以读入其他makefile文件的内容,效果就如同在include的位置用对应的文件内容替换一样。
1 | include mkf1 mkf2 # 可以引入多个文件,用空格隔开 |
如果找不到对应文件,则会报错,如果要忽略错误,可以在include
前加-
1 | -include mkf1 mkf2 |
应用实例:自动生成依赖
1 | objs = block.o command.o input.o main.o scene.o test.o |
嵌套make
如果将一个大项目分为许多小项目,则可以使用嵌套(递归)使用make。具体做法为,写一个总的Makefile,然后在每个子项目中都写一个Makefile,在总Makefile中进行调用。
例如,可以把sudoku项目中除main.cpp,test.cpp外的其他cpp存为一个子项目,编译为一个库文件,main.cpp test.cpp为另一个子项目,编译为.o然后链接库文件成可执行文件:
库文件Makefile
1 | vpath %.h ../include |
main.cpp test.cpp的Makefile
1 | CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8 |
总的Makefile
1 | .PHONY: all clean |
其中
1 | $(MAKE) -C subdir |
这一指令会自动进入subdir文件夹然后执行make。
可以通过export
指令向子项目的make传递变量。
1 | export var # 传递var |