0. 官方文档
- GNU Make 官方网站: https://www.gnu.org/software/make
- GNU Make 官方文档下载地址: https://www.gnu.org/software/make/manual/
- Makefile Tutorial:https://makefiletutorial.com/
1.基本要求
1.1 基本格式
targets : prerequisties
[tab键]
command
- target : 目标文件,可以.o后缀的目标文件,也可以是执行文件,还可以是一个标签(Label),对于标签这种特性,在后续的“伪标签”章节会有叙述
- prerequisties:要生成的那个target所需要的文件或目标
- command: 是make需要执行的命令
通常我们会将一堆.c/.cpp
文件放在prerequisties
, targets 放对应需要生成的.o
文件。执行的command
对应就是g++ - c $(prerequisties) -o $(argets)
。同样链接可执行文件的时候也是一样的,targets
放可执行文件,prerequisties则放目标.o文件
注意
: command的前面需要用tab
,而不是空格键。makefile对格式的控制是非常严格的
案例
通常在makefile中,会希望有个语句debug
, 用来打印一些需要的信息,比如变量的值或者一些函数的处理结果。
Makefile
中的内容如下
debug:
echo hello
cd 到Makefile
所在目录,然后执行make debug
就可以输出结果了。
可以看到输出了hello
结果,同时也输出了command
命令。随着工程越来越大,我们的命令会变得复杂。都打印出所有command
终端看起来会显得冗余
。 可以在命令前加上@
符号,就可以不在终端输出command
,只输出结果。
debug:
@echo hello
1.2 Makefile规则
make
会在当前目录下找到一个名字叫Makefile
或者makefile
的文件- 在执行make命令时,如果没有指定target(
make xx (target)
),它会找到文件中第一个目标文件(target
), 并把这个文件作为最终的目标文件 - 如果
target
文件不存在
,或者target文件依赖的.o
文件(prerequities)的文件修改时间要比target这个文件新,就会执行后面所定义的命令command
来生成target文件 - 如果target依赖的
.o
文件(prerequities) 文件也不存在,make会在当前文件中找到target为.o
文件的依赖项,并根据对应的command
生成.o
文件。
makefile会根据依赖,一层层去执行,直到最终生成我们需要的target。makefile中一旦源文件做了更改(.cpp/.c), 它会自动重新编译一遍,这其实是非常方便的。
1.3 伪目标
伪目标
不是一个文件,它只是一个标签
,我们需要显示地指明这个目标
才能让其生效。- 伪目标的取名不能和文件名重名,否则不会执行命令。(比如与makefile同目录存在一个与伪目标
同名
的clean
文件,此时我们想 执行make clean
删除 文件夹objs
,则会无法删除
为了避免和文件重名
的情况,我们可以使用一个特殊的标记.PHONY
来显示地指明一个目标为伪目标
,向make说明,不管是否存在该文件,都不影响该伪目标的执行。通常在一个makefile里面,对于clean,debug,run
等命令, 我们一般都会写在.PHONY
后面, 然后就可以和make 一起当作命令去执行了。
.PHONY:clean debug run
注意
:如果出现报错
,可能是使用了空格,重新tab
下
2.变量的定义与使用
变量在声明时候需要给予初值, 在使用时,需要给变量名前加上$
符号,并以()
把变量给包括起来。
2.1 变量的定义
cpp := src/main.cpp
obj := objs/main.o
2.2 变量的引用
- 可以使用() 或者{}
cpp := src/main.cpp
obj := objs/main.o
$(obj) : $(cpp)
@g++ -c $(cpp) -o $(obj)
# make compile可执行编译
compile : $(obj)
# 打印调试信息
debug :
@echo $(cpp)
@echo $(obj)
在终端运行make compile
命令,可执行编译过程。编译完后,在objs下会生成main.o目标文件
2.3 预定义的变量
除了自定义的变量,makefile还提供了一些预定义的变量,可以很方便减少command
中的语句。
$@
: 目标(target)的完整名称$<
: 第一个依赖的文件(prerequisties)的名称$^
: 所有的依赖文件(prerequisties), 以空格分开,不包括重复的依赖文件
cpp := src/main.cpp
obj := objs/main.o
$(obj) : $(cpp)
@g++ -c $< -o $@
@echo $^
compile: $(obj)
clean:
@rm -rf objs/main.o
debug :
@echo $(cpp)
@echo $(obj)
# 为了防止和文件冲突,添加伪目标标识
.PHONY : compile clean
执行
# 清除之前编译生成的.o文件
make clean
# 重新编译
make compile
3. 常用的符号
(1) =
- 简单的赋值运算符
- 用于将右边的值分配给左边变量
- 如果在后面的语句中重新定义了了该变量,则将使用新的遍历
示例
HOST_ARCH = aarch64
TARGET_ARCH = ${HOST_ARCH}
#....
#....
HOST_ARCH = amd64
debug:
@echo ${TARGET_ARCH}
终端执行:
make debug
>> amd64
可以看到TARGET_ARCH
的值随着HOST_ARCH的变化更新了
(2) :=
- 立即赋值运算符
- 用于在
定义变量
时立即求值 - 该值在定义后不再更改 (与
=
的区别) - 即使在后面的语句中重新定义了该变量
示例
HOST_ARCH := aarch64
TARGET_ARCH := ${HOST_ARCH}
#....
#....
HOST_ARCH := amd64
debug:
@echo ${TARGET_ARCH}
终端执行:
make debug
>> aarch64
可以看到打印出来的是最初赋的值aarch64
(3) ?=
- 默认赋值运算符
- 如果该变量已经定义,则不进行任何操作
- 如果该变量尚未定义,则求值并分配
示例
HOST_ARCH = aarch64
HOST_ARCH ?= amd64
debug:
@echo ${HOST_ARCH}
- 输出的是
aarch64
,已经赋值了,就不会进行赋值操作。
#HOST_ARCH = aarch64
HOST_ARCH ?= amd64
debug:
@echo ${HOST_ARCH}
- 此时输出的是
amd64
,因为没有赋值的话,就会进行赋值
需要注意下,如下也是赋值了的,只是赋值了一个空的字符串
HOST_ARCH =
(4) 累加+=
+=
是非常常用的,比如在编译的时候,添加头文件路径,库的路径,名字以及c++编译的选项等
,就会需要进行累加的操作,添加完成就相当于将所有字符串,保存称为一个字符串数组或列表中。
示例
include_path := src
CXXFLAGS := -m64 -fPIC -g -o0 -std=c++11 -w -fopenmp
CXXFLAGS += $(include_path)
debug :
@echo ${CXXFLAGS}
.PHONY: debug
终端执行:
>> -m64 -fPIC -g -o0 -std=c++11 -w -fopenmp src
可以看到将src
加到了最后。
(4) 续行符\
后面会用的库越来越多,包含头文件的路径,库文件的路径也会越来越多,所以都会用续行符\
去写,注意\
前面可以用空格隔开,后面不需要空格
,
LDLIBS := cudart opencv_core gomp \
nvinfer protobuf cudnn pthread \
cublas nvcaffe_parser nvinfer_plugin
4. 常用函数
函数的调用,很像变量的使用,也是用$
来标识的,其语法如下:
$(fn,arguments) or ${fn,arguments}
- fn函数名
- argument: 函数参数,
参数间以逗号分隔
,而函数名和参数之间以空格分隔
(1) shell
shell
函数是一个非常常用的函数,可以让我们在makefile里面执行终端bash
的命令
${shell <command> <arguments>}
- 名称: shell 命令函数 —shell
- 功能: 调用shell命令 command
- 返回: 函数返回shell 命令command的执行结果
示例
查找src
目录下所有.cpp
文件
cpp_srcs := ${shell find src -name *.cpp} #src 目录 -name 以名字查找 *.cpp 所有以.cpp结尾
debug :
@echo ${cpp_srcs}
.PHONY : debug
记得如果debug是个命令,而不是生成的文件的话,记得加上.PHONY
的标识符.
终端执行:
make debug
就可以拿到src
目录下全部的.cpp
文件路径