makfile使用
Makefile
- GNU Make 官方网站:https://www.gnu.org/software/make/
- GNU Make 官方文档下载地址:https://www.gnu.org/software/make/manual/
- Makefile Tutorial:https://makefiletutorial.com/
1 基本格式
targets : prerequisties
[tab键]command
- target:目标文件,可以是 OjectFile,也可以是执行文件,还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
- prerequisite:要生成那个 target 所需要的文件或是目标。
- command:是 make 需要执行的命令,
2 Makefile 规则
- make 会在当前目录下找到一个名字叫
Makefile
或makefile
的文件 - 如果找到,它会找文件中第一个目标文件(target),并把这个文件作为最终的目标文件
- 如果 target 文件不存在,或是 target 文件依赖的 .o 文件(prerequities)的文件修改时间要比 target 这个文件新,就会执行后面所定义的命令 command 来生成 target 这个文件
- 如果 target 依赖的 .o 文件(prerequisties)也存在,make 会在当前文件中找到 target 为 .o 文件的依赖性,如果找到,再根据那个规则生成 .o 文件
3 伪目标
“伪目标” 不是一个文件,只是一个标签。我们要显示地指明这个 “目标” 才能让其生效
“伪目标” 的取名不能和文件名重名,否则不会执行命令
为了避免和文件重名的这种情况,我们可以使用一个特殊的标记 .PHONY
来显示地指明一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是 “伪目标”
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行 “clean” 这个目标,只有"make clean" 这个命令
Makefile 的变量
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $
符号,并用小括号 ()
把变量给包括起来。
1 变量的定义
cpp := src/main.cpp
obj := objs/main.o
2 变量的引用
- 可以用
()
或{}
cpp := src/main.cpp
obj := objs/main.o
$(obj) : ${cpp}
@g++ -c $(cpp) -o $(obj)
compile : $(obj)
3 预定义变量
$@
: 目标(target)的完整名称$<
: 第一个依赖文件(prerequisties)的名称$^
: 所有的依赖文件(prerequisties),以空格分开,不包含重复的依赖文件
cpp := src/main.cpp
obj := objs/main.o
$(obj) : ${cpp}
@g++ -c $< -o $@
@echo $^
compile : $(obj)
.PHONY : compile
Makefile 常用符号
1 =
- 简单的赋值运算符
- 用于将右边的值分配给左边的变量
- 如果在后面的语句中重新定义了该变量,则将使用新的值
示例
HOST_ARCH = aarch64
TARGET_ARCH = $(HOST_ARCH)
# 更改了变量 a
HOST_ARCH = amd64
debug:
@echo $(TARGET_ARCH)
2 :=
- 立即赋值运算符
- 用于在定义变量时立即求值
- 该值在定义后不再更改
- 即使在后面的语句中重新定义了该变量
示例
HOST_ARCH := aarch64
TARGET_ARCH := $(HOST_ARCH)
# 更改了变量 a
HOST_ARCH := amd64
debug:
@echo $(TARGET_ARCH)
3 ?=
- 默认赋值运算符
- 如果该变量已经定义,则不进行任何操作
- 如果该变量尚未定义,则求值并分配
HOST_ARCH = aarch64
HOST_ARCH ?= amd64
debug:
@echo $(HOST_ARCH)
4 累加 +=
CXXFLAGS := -m64 -fPIC -g -O0 -std=c++11 -w -fopenmp
CXXFLAGS += $(include_paths)
5 \
- 续行符
示例
LDLIBS := cudart opencv_core \
gomp nvinfer protobuf cudnn pthread \
cublas nvcaffe_parser nvinfer_plugin
6 * 与 %
*
: 通配符表示匹配任意字符串,可以用在目录名或文件名中%
: 通配符表示匹配任意字符串,并将匹配到的字符串作为变量使用
Makefile 的常用函数
函数调用,很像变量的使用,也是以 “$” 来标识的,其语法如下:
$(fn, arguments) or ${fn, arguments}
- fn: 函数名
- arguments: 函数参数,参数间以逗号
,
分隔,而函数名和参数之间以“空格”分隔
1 shell
$(shell <command> <arguments>)
- 名称:shell 命令函数 —— shell
- 功能:调用 shell 命令 command
- 返回:函数返回 shell 命令 command 的执行结果
示例
# shell 指令,src 文件夹下找到 .cpp 文件
cpp_srcs := $(shell find src -name "*.cpp")
# shell 指令, 获取计算机架构
HOST_ARCH := $(shell uname -m)
2 subst
$(subst <from>,<to>,<text>)
- 名称:字符串替换函数——subst
- 功能:把字串 <text> 中的 <from> 字符串替换成 <to>
- 返回:函数返回被替换过后的字符串
示例:
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(subst src/,objs/,$(cpp_objs))
3 patsubst
$(patsubst <pattern>,<replacement>,<text>)
- 名称:模式字符串替换函数 —— patsubst
- 功能:通配符
%
,表示任意长度的字串,从 text 中取出 patttern, 替换成 replacement - 返回:函数返回被替换过后的字符串
示例
cpp_srcs := $(shell find src -name "*.cpp") #shell指令,src文件夹下找到.cpp文件
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs)) #cpp_srcs变量下cpp文件替换成 .o文件
4 foreach
$(foreach <var>,<list>,<text>)
- 名称:循环函数——foreach。
- 功能:把字串<list>中的元素逐一取出来,执行<text>包含的表达式
- 返回:<text>所返回的每个字符串所组成的整个字符串(以空格分隔)
示例:
library_paths := /datav/shared/100_du/03.08/lean/protobuf-3.11.4/lib \
/usr/local/cuda-10.1/lib64 \
library_paths := $(foreach item,$(library_paths),-L$(item))
同等效果
I_flag := $(include_paths:%=-I%)
5 dir
$(dir <names...>)
- 名称:取目录函数——dir。
- 功能:从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前
的部分。如果没有反斜杠,那么返回“./”。 - 返回:返回文件名序列的目录部分。
- 示例:
mkdir -p $(dir src/foo.c ) # 返回值是“src/ ./”。创建对应的目录,-p 代表不存在即创建
6 notdir
$(notdir <names...>)
将当前字符串去掉路径,只保留名字
示例
libs := $(notdir $(shell find /usr/lib -name lib*))
7 filter
$(filter <names...>)
可以返回对应的文件名,可以用于查找
libs := $(notdir $(shell find /usr/lib -name lib*))
a_libs := $(filter %.a,$(libs))
so_libs := $(filter %.so,$(libs))
8 basename
$(basename <names...>)
会将所有文件的后缀去掉并且返回。
libs := $(notdir $(shell find /usr/lib -name lib*))
a_libs := $(subst lib,,$(basename $(filter %.a,$(libs))))
so_libs := $(subst lib,,$(basename $(filter %.so,$(libs))))
Conditional Rules
注意:
- Condition 语句里面全部不能用 Tab 缩进, 你看到的 Makefile 如果好像有 “Tab”, 那全部是空格
- 使用 Tab 会报错:*** commands commence before first target
1 ifeq / else / endif
# build flags
ifeq ($(TARGET_OS),darwin)
LDFLAGS += -rpath $(CUDA_PATH)/lib
CCFLAGS += -arch $(HOST_ARCH)
else ifeq ($(HOST_ARCH)-$(TARGET_ARCH)-$(TARGET_OS),x86_64-armv7l-linux)
LDFLAGS += --dynamic-linker=/lib/ld-linux-armhf.so.3
CCFLAGS += -mfloat-abi=hard
else ifeq ($(TARGET_OS),android)
LDFLAGS += -pie
CCFLAGS += -fpie -fpic -fexceptions
endif
2 ifneq / else / endif
HOST_ARCH := $(shell uname -m)
TARGET_ARCH ?= $(HOST_ARCH)
temp := $(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le armv7l)
ifneq (,$(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le armv7l))
ifneq ($(TARGET_ARCH),$(HOST_ARCH))
ifneq (,$(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le))
TARGET_SIZE := 64
else ifneq (,$(filter $(TARGET_ARCH),armv7l))
TARGET_SIZE := 32
endif
else
TARGET_SIZE := $(shell getconf LONG_BIT)
endif
else
$(error ERROR - unsupported value $(TARGET_ARCH) for TARGET_ARCH!)
endif
3 ifdef / else / endif
ifdef TARGET_OVERRIDE # cuda toolkit targets override
NVCCFLAGS += -target-dir $(TARGET_OVERRIDE)
endif
Compile
1 编译过程
1.1 预处理
示例
cpp_srcs := $(shell find src -name *.cpp)
pp_files := $(patsubst src/%.cpp,src/%.i,$(cpp_srcs))
src/%.i : src/%.cpp
@g++ -E $^ -o $@
preprocess : $(pp_files)
clean :
@rm -f src/*.i
debug :
@echo $(pp_files)
.PHONY : debug preprocess clean
1.2 编译成汇编语言
示例
cpp_srcs := $(shell find src -name *.cpp)
as_files := $(patsubst src/%.cpp,src/%.s,$(cpp_srcs))
src/%.s : src/%.cpp
@g++ -S $^ -o $@
assemble : $(as_files)
clean :
@rm -f src/*.s
debug :
@echo $(as_files)
.PHONY : debug assemble clean
1.3 编译成目标文件
示例
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 $@
objects : $(cpp_objs)
clean :
@rm -rf objs src/*.o
debug :
@echo $(as_files)
.PHONY : debug objects clean
1.4 链接可执行文件
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 $@
workspace/exec : $(cpp_objs)
@mkdir workspace/exec
@g++ $^ -o $@
run : workspace
@./$<
clean :
@rm -rf objs workspace/exec
debug :
@echo $(as_files)
.PHONY : debug run clean
2 编译选项
编译选项
-m64
: 指定编译为 64 位应用程序-std=
: 指定编译标准,例如:-std=c++11、-std=c++14-g
: 包含调试信息-w
: 不显示警告-O
: 优化等级,通常使用:-O3-I
: 加在头文件路径前fPIC
: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的
链接选项
-l
: 加在库名前面-L
: 加在库路径前面-Wl,<选项>
: 将逗号分隔的 <选项> 传递给链接器-rpath=
: “运行” 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找
3 Implicit Rules
- CC: Program for compiling C programs; default cc
- CXX: Program for compiling C++ programs; default g++
- CFLAGS: Extra flags to give to the C compiler
- CXXFLAGS: Extra flags to give to the C++ compiler
- CPPFLAGS: Extra flags to give to the C preprocessor
- LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker
4 编译带头文件的程序
add.hpp
#ifndef ADD_HPP
#define ADD_HPP
int add(int a, int b);
#endif // ADD_HPP
add.cpp
int add(int a, int b)
{
return a+b;
}
minus.hpp
#ifndef MINUS_HPP
#define MINUS_HPP
int minus(int a, int b);
#endif // MINUS_HPP
minus.cpp
int minus(int a, int b)
{
return a-b;
}
main.cpp
#include <stdio.h>
#include "add.hpp"
#include "minus.hpp"
int main()
{
int a=10; int b=5;
int res = add(a, b);
printf("a + b = %d\n", res);
res = minus(a, b);
printf("a - b = %d\n", res);
return 0;
}
Makefile
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))
# 你的头文件所在文件夹路径(建议绝对路径)
include_paths :=
I_flag := $(include_paths:%=-I%)
objs/%.o : src/%.cpp
@mkdir -p $(dir $@)
@g++ -c $^ -o $@ $(I_flag)
workspace/exec : $(cpp_objs)
@mkdir -p $(dir $@)
@g++ $^ -o $@
run : workspace/exec
@./$<
debug :
@echo $(I_flag)
clean :
@rm -rf objs
.PHONY : debug run
Makefile 静态库编译
1 程序
add.hpp
#ifndef ADD_HPP
#define ADD_HPP
int add(int a, int b);
#endif // ADD_HPP
add.cpp
int add(int a, int b)
{
return a+b;
}
minus.hpp
#ifndef MINUS_HPP
#define MINUS_HPP
int minus(int a, int b);
#endif // MINUS_HPP
minus.cpp
int minus(int a, int b)
{
return a-b;
}
main.cpp
#include <stdio.h>
#include "add.hpp"
#include "minus.hpp"
int main()
{
int a=10; int b=5;
int res = add(a, b);
printf("a + b = %d\n", res);
res = minus(a, b);
printf("a - b = %d\n", res);
return 0;
}
2 编译过程
- 源文件[.c/cpp] -> Object文件[.o]
g++ -c [.c/cpp][.c/cpp]... -o [.o][.o]... -I[.h/hpp] -g
- Object文件[.o] -> 静态库文件[lib库名.a]
ar -r [lib库名.a] [.o][.o]...
- main 文件[.c/cpp] -> Object 文件[.o]
g++ -c [main.c/cpp] -o [.o] -I[.h/hpp]
- 链接 main 的 Object 文件与静态库文件 [lib库名.a]
g++ [main.o] -o [可执行文件] -l[库名] -L[库路径]
动态库(共享库)
1 编库
编译 .c 文件
源文件[.c/cpp] -> Object文件[.o]
g++ -c [.c/cpp][.c/cpp]... -o [.o][.o]... -I[.h/hpp] -g -fpic
Object文件[.o] -> 动态库文件[lib库名.so]
g++ -shared [.o][.o]... -o [lib库名.so]
main文件[.c/cpp] -> Object文件[.o]
g++ -c [main.c/cpp] -o [.o] -I[.h/hpp] -g
2 链接
链接 main 的 Object 文件与动态库文件[lib库名.so]
g++ [main.o] -o [可执行文件] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
常见 Error
*** missing separator. Stop.
- 原因: Makefile 语法出错
- 解决方法: 根据报错的行数,检查 tab 缩进,空格问题
*** commands commence before first target. Stop
- 原因: if等语句里面用了 tab 缩进
- 解决方法: 缩进的地方全部改为空格