makefile 学习(4): makefile基础

0. 官方文档

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,则会无法删除
    【图2】

为了避免和文件重名的情况,我们可以使用一个特殊的标记.PHONY来显示地指明一个目标为伪目标,向make说明,不管是否存在该文件,都不影响该伪目标的执行。通常在一个makefile里面,对于clean,debug,run等命令, 我们一般都会写在.PHONY后面, 然后就可以和make 一起当作命令去执行了。

.PHONY:clean debug run

【图3】

注意:如果出现报错,可能是使用了空格,重新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文件路径

(2)subst 函数

subst是一个字符串替换的函数。

语法:

$[subst <from>,<to>,<text>}
  • 名称: 字符串替换函数——subst
  • 功能: 把字符串 中的字符串替换成
  • 返回: 被替换后的字符串

示例:

  • src/*.cpp替换为objs/*.coo
cpp_srcs := ${shell find src -name *.cpp}
cpp_objs := ${subst src/,objs/,${cpp_srcs}}

debug :
	@echo ${cpp_objs}

.PHONY : debug

**[图1]**

  • 可以看到输出所有.cpp文件路径中的src/字符串全部替换objs/

  • 同时将得到的objs/*.cpp,替换为objs/*.o, 实现如下:

cpp_srcs := ${shell find src -name *.cpp}
cpp_objs := ${subst src/,objs/,${cpp_srcs}}
cpp_objs := ${subst .cpp,.o,${cpp_objs}}

debug :
	@echo ${cpp_srcs}
	@echo ${cpp_objs}

.PHONY : debug
  • 可以看到成功将objs/下的.cpp后缀替换为.o后缀
    图【2】

通过subst可以帮我们批量操作字符串,非常方便

(3) patsubst 函数

patsubst 可以按照一定的模式对字符串进行替换

语法

${patsubst <pattern>,<replacement>,<text>}
  • 名称:模式字符串替换函数——patsubst
  • 功能: 通配符%,表示任意的字符串,从text中取出pattern,替换成replacement
  • 返回:返回被替换后的字符串

示例

cpp_srcs := ${shell find src -name *.cpp}
# cpp_objs := ${subst src/,objs/,${cpp_srcs}}
# cpp_objs := ${subst .cpp,.o,${cpp_objs}}
cpp_objs := ${patsubst src/%.cpp,objs/%.o,${cpp_srcs}}

debug :
	@echo ${cpp_srcs}
	@echo ${cpp_objs}

.PHONY : debug

图【3】

这样的话,一行就完成了之前两行才能完成替换的任务,src/%.cpp中的%表示src/.cpp中的任意字符串。通过patsubst可以极大的简化我们的工作。

(4) foreach函数

foreach函数非常有用,尤其当我们编译大型项目时。

语法

${foreach <var>,<list>,<text>}
  • 名称: 循环函数——foreach
  • 功能: 把<list> 中的元素逐一取出来,执行<text>包含的表达式

示例:

  • 如果项目需要依赖其他库的话,就需要使用Include添加头文件的搜索路径。
  • 并在编译时,将每个搜索路径加上-I
  • 对于路径只有2,3个,手动添加是ok的,如果Include 包含路径很多的话,为了方便建议使用foreach来遍历进行添加-I选项
include_paths := /usr/include \
				 /usr/include/opencv2/core

include_paths := ${foreach item,${include_paths},-I${item}}

debug :
	@echo ${include_paths}

.PHONY : debug

【图4】

  • 可以看到Include的每个搜索路径都加上了-I选项, 这样就非常遍历。 同理libs也可以利用foreach加上-l选项。

  • 这里介绍一种更为简便的方法,同样实现对每个include搜索路径加上-I选项, 完整代码如下:

include_paths := /usr/include \
				 /usr/include/opencv2/core

#include_paths := ${foreach item,${include_paths},-I${item}}
I_flag := ${include_paths:%=-I%}

debug :
	@echo ${I_flag}

.PHONY : debug

【图5】

可以看到和foreach遍历添加,实现的效果是一样的

(5) dir函数

dir: 取目录函数

  • 功能:从文件名中取出目录部分

语法:

${dir <names ...>}

示例:
实现将src下面的所有.cpp文件,编译为对应的.o文件,并存放在objs/目录下:

cpp_srcs := ${shell find src -name *.cpp}
cpp_objs :=${patsubst src/%.cpp,objs/%.o,${cpp_srcs}}

objs/%.o : src/%.cpp
	@g++ -c $^ -o $@

compile : ${cpp_objs}

debug:
	@echo ${cpp_srcs}
	@echo ${cpp_objs}

.PHONY : debug compile

假设此时没有创建objs目录,由于编译的时候会编译objs目录下面的文件,当前没有objs目录的话,就会报错,如下:

【图6】

此时可以利用mkdir来创建目录,注意加上-p选项,当目录存在时,也不会报错。利用dir函数,取文件对应的目录。其中$@代表目标文件:objs/%.o,它的目录就是objs/

	@mkdir -p $(dir $@)

这样先创建好了文件夹,再去编译就不会报错了,完整代码如下:

cpp_srcs := ${shell find src -name *.cpp}
cpp_objs :=${patsubst src/%.cpp,objs/%.o,${cpp_srcs}}

objs/%.o : src/%.cpp
	@mkdir -p ${dir $@}
	@g++ -c $^ -o $@

compile : ${cpp_objs}

debug:
	@echo ${cpp_srcs}
	@echo ${cpp_objs}

.PHONY : debug compile

这样就在objs下为每个src中的.cpp文件,创建了对应的.o文件。

(6) notdir 函数

去掉文件路径中的目录

示例

  • 找打/usr/lib下面的所有库。
libs := ${shell find /usr/lib -name lib*}

debug :
	@echo ${libs}

.PHONY : debug

ku

  • 利用notdir去掉路径

libs := ${notdir $(shell find /usr/lib -name lib*)}

debug :
	@echo ${libs}

.PHONY : debug

(7) filter 函数

按要求过滤掉文件

  • 找到所有的静态库(.a结尾的)
  • 找到所有的动态库(.so结尾的)

libs := ${notdir $(shell find /usr/lib -name lib*)}

a_libs := ${filter %.a,${libs}}
so_libs := ${filter %.so,${libs}}

debug :
	@echo ${a_libs}
	@echo ${so_libs}

.PHONY : debug

(8) basename函数

  • 去掉路径中的后缀, 将.so,.a后缀去掉

libs := ${notdir $(shell find /usr/lib -name lib*)}

a_libs := ${basename ${filter %.a,${libs}}}
so_libs := ${basename ${filter %.so,${libs}}}

debug :
	@echo ${a_libs}
	@echo ${so_libs}

.PHONY : debug
  • 去掉lib开头,只保留库名, 将lib替换为空:${subst lib,,${a_libs}}

libs := ${notdir $(shell find /usr/lib -name lib*)}

a_libs := ${subst lib,,${basename ${filter %.a,${libs}}}}
so_libs := ${subst lib,,{basename ${filter %.so,${libs}}}}

debug :
	@echo ${a_libs}
	@echo ${so_libs}

.PHONY : debug

这样就拿到了干干净净的库名,没有路径,没有后缀,也没有lib前缀

猜你喜欢

转载自blog.csdn.net/weixin_38346042/article/details/130741699