参考自:
Makefile
的过程中,需要了解一些底层的细节,包括链接器
,编译器
等等。Makefile可以被配置成在所有的平台上都能够使用,非常适合轻量级项目和较小的项目
。CMake
则是一种构建工具的高级语言,可以自动生成适合各种平台和编译器的Makefile文件
,同时也能生成Visual Studio等IDE所需要的工程文件。CMake使用的是一种高级的、面向目标的语言,可以实现更加复杂的构建任务。CMake的优点在于它可以跨平台地生成Makefile文件,同时具有很好的可读性和可维护性,非常适合大型的、复杂的项目。CMake的缺点在于它的学习曲线相对较陡,需要学习一些新的概念和语法。(1) 一个makefile的demo
#include
using namespace std;
int main()
{
cout << "Hello CUDA" << endl;
return 0;
}
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
cpp_objs := $(subst src/,objs/,$(cpp_objs))
debug :
@echo $(cpp_objs)
(2) 逐行解释这个小demo
cpp_srcs
,它的值是通过调用find
命令在 src 目录下查找所有以 .cpp 结尾的文件,并将它们的路径存入 cpp_srcs 变量中。cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs
,它的值是将 cpp_srcs 变量中所有 .cpp
后缀的文件名都替换成以.o
结尾的目标文件名,并将这些目标文件名保存到 cpp_objs 变量中。cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
cpp_objs
变量中的所有 src/ 替换为 objs/,并将替换后的值存储回 cpp_objs 变量cpp_objs := $(subst src/,objs/,$(cpp_objs))
debug :
@echo $(cpp_objs)
# 定义cpp源码路径,并转换为objs目录先的o文件
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
cpp_objs := $(subst src/,objs/,$(cpp_objs))
# 定义cu源码路径,并转换为objs目录先的cuo文件
# 如果cpp文件和cu名字一样,把.o换成.cuo
cu_srcs := $(shell find src -name "*.cu") # 全部src下的*.cu存入变量cu_srcs
cu_objs := $(patsubst %.cu,%.cuo,$(cu_srcs)) # cu_srcs中全部.cu换成.o
cu_objs := $(subst src/,objs/,$(cu_objs)) # cu_objs src/换成objs/
# 定义名称参数
workspace := workspace
binary := pro
# makefile中定义cpp的编译方式
# $@:代表规则中的目标文件(生成项)
# $<:代表规则中的第一个依赖文件
# $^:代表规则中的所有依赖文件,以空格分隔
# $?:代表规则中所有比目标文件更新的依赖文件,以空格分隔
# 定义cpp文件的编译方式
# @echo Compile $< 输出正在编译的源文件的名称
objs/%.o : src/%.cpp
@mkdir -p $(dir $@)
@echo Compile $<
@g++ -c $^ -o $@
# 定义.cu文件的编译方式
objs/%.cuo : src/%.cu
@mkdir -p $(dir $@)
@echo Compile $<
@nvcc -c $^ -o $@
# 定义workspace/pro文件的编译
$(workspace)/$(binary) : $(cpp_objs) $(cu_objs)
@mkdir -p $(dir $@)
@echo Link $^
@g++ $^ -o $@
# 定义pro快捷编译指令,这里只发生编译,不执行
# 快捷指令就是make pro
pro : $(workspace)/$(binary)
# 定义指令并且执行的指令,并且执行目录切换到workspace下
run : pro
@cd $(workspace) && ./$(binary)
debug :
@echo $(cpp_objs)
@echo $(cu_objs)
这里报错了,因为缺少了cuda的库文件,下面是查看自己cuda版本和找到在哪里的指令,以我的CUDA11.7为例, 去到目录下看看有什么库文件,我们当前需要一个cudaruntime的头文件
nvcc --version
whereis cuda-11.7
cuda-11: /usr/local/cuda-11.7
cd /usr/local/cuda-11.7/lib64
来看看最后要完成的工程目录
project/
- src/
- main.cpp
- 01.kernel.cu
- objs/
- workspace/
- Makefile
(1) 定义源码路径
.cpp
和.cu
的文件,然后将它们分别存储到cpp_srcs
和cu_srcs
变量中。接着,使用patsubst
和subst
函数将源码文件路径中的src/替换成objs/,并将它们分别存储到cpp_objs和cu_objs变量中,这样就得到了在objs目录下存储的目标文件列表。在后面的编译和链接过程中,这些目标文件(*.o, *.cuo)会被用来生成最终的可执行文件。 objs
目录中放置的是编译生成的目标文件
,对于这个 makefile 来说,所有的 .o
和.cuo
文件都会被放到 objs
目录下。在链接生成可执行文件时,makefile 会从这些目标文件中找到需要的文件进行链接
。# 定义cpp源码路径,并转换为objs目录先的o文件
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
cpp_objs := $(subst src/,objs/,$(cpp_objs))
# 定义cu源码路径,并转换为objs目录先的cuo文件
# 如果cpp文件和cu名字一样,把.o换成.cuo
cu_srcs := $(shell find src -name "*.cu") # 全部src下的*.cu存入变量cu_srcs
cu_objs := $(patsubst %.cu,%.cuo,$(cu_srcs)) # cu_srcs中全部.cu换成.o
cu_objs := $(subst src/,objs/,$(cu_objs)) # cu_objs src/换成objs/
(2) 定义可执行文件及存放位置
这里定义的名称参数 workspace 和 binary 是用来指定工作空间和生成的可执行文件名称的。在这个 Makefile 中,workspace
表示的是工作空间的目录名称,binary
表示生成的可执行文件的名称。这些参数可以在 Makefile 中的其它规则中使用,例如在链接时指定目标文件路径和生成的可执行文件名称
等。
workspace := workspace
binary := pro
(3) 定义头文件,库文件和链接目标
libcudart.so
这个库,cudart 是 CUDA runtime库,CUDA编程必须用到的whereis cuda-
版本查找位置头文件
,库文件
,include_path
, library_path
告诉编译器去哪里找头文件,库文件link_librarys
是告诉链接器需要链接那些库文件# 定义头文件库文件和链接目标,后面用foreach一次性增加
include_paths := /usr/local/cuda-11.7/include
library_paths := /usr/local/cuda-11.7/lib64
link_librarys := cudart
(4) 定义编译选项
# 定义编译选项
cpp_compile_flags := -m64 -fPIC -g -O0 -std=c++11
cu_compile_flags := -m64 -g -O0 -std=c++11
(5)合并选项
-L
指定链接时查找的目录-l
指定链接的目标名称,符合libname.so -lname 规则-I
指定编译时头文件查找目录run path
链接的时查找动态链接库文件的路径,让程序运行的时候,自动查找并加载动态链接库rpath := $(foreach item,$(link_librarys),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))
(6) 把合并后的选项给到编译器选项
cpp_compile_flags += $(include_paths)
: 将include_paths添加到cpp_compile_flags
中,用于在编译C++源代码时指定头文件搜索路径。cu_compile_flags += $(include_paths)
: 将include_paths
添加到cu_compile_flags
中,用于在编译CUDA源代码时指定头文件搜索路径。link_flags := $(rpath) $(library_paths) $(link_librarys)
: 将rpat
h、library_paths
、link_librarys
合并成一个链接选项link_flags。rpath指定运行时库搜索路径,library_paths指定链接库搜索路径,link_librarys指定要链接的库文件名。cpp_compile_flags += $(include_paths)
cu_compile_flags += $(include_paths)
link_flags := $(rpath) $(library_paths) $(link_librarys)
(7) 定义cpp cuda编译方式
.cpp .cu
源文件 编译成目标文件 .o .cuo
,放在objs
里面.cpp, .cu
源文件是依赖项,生成目标文件.o .cuo
# 定义cpp文件的编译方式
# @echo Compile $< 输出正在编译的源文件的名称
objs/%.o : src/%.cpp
@mkdir -p $(dir $@)
@echo Compile $<
@g++ -c $^ -o $@ $(cpp_compile_flags)
# 定义.cu文件的编译方式
objs/%.cuo : src/%.cu
@mkdir -p $(dir $@)
@echo Compile $<
@nvcc -c $^ -o $@ $(cu_compile_flags)
(8) 在workspace下编译出可执行文件
-L./objs
表示告诉链接器在当前目录下寻找库文件,./objs 是指定的路径。实际上,./objs 是目标文件存储的路径,不是库文件存储的路径,这里写的是 -L./objs 只是为了指定链接时查找目录的路径。# 定义workspace/pro文件的编译
$(workspace)/$(binary) : $(cpp_objs) $(cu_objs)
@mkdir -p $(dir $@)
@echo Link $^
@g++ $^ -o $@ $(link_flags) -L./objs
(9)定义伪标签, 作为指令
# 定义pro快捷编译指令,这里只发生编译,不执行
# 快捷指令就是make pro
pro : $(workspace)/$(binary)
# 定义指令并且执行的指令,并且执行目录切换到workspace下
run : pro
@cd $(workspace) && ./$(binary)
debug :
@echo $(cpp_objs)
@echo $(cu_objs)
clean :
@rm -rf objs $(workspace)/$(binary)
# 指定伪标签,作为指令
.PHONY : clean debug run pro
(10) 完整的makefile文件
# 定义cpp源码路径,并转换为objs目录先的o文件
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
cpp_objs := $(subst src/,objs/,$(cpp_objs))
# 定义cu源码路径,并转换为objs目录先的cuo文件
# 如果cpp文件和cu名字一样,把.o换成.cuo
cu_srcs := $(shell find src -name "*.cu") # 全部src下的*.cu存入变量cu_srcs
cu_objs := $(patsubst %.cu,%.cuo,$(cu_srcs)) # cu_srcs中全部.cu换成.o
cu_objs := $(subst src/,objs/,$(cu_objs)) # cu_objs src/换成objs/
# 定义可执行文件存放目录以及可执行文件名
workspace := workspace
binary := pro
# 定义头文件库文件和链接目标,后面用foreach一次性增加
include_paths := /usr/local/cuda-11.7/include
library_paths := /usr/local/cuda-11.7/lib64
link_librarys := cudart
# 定义编译选项
cpp_compile_flags := -m64 -fPIC -g -O0 -std=c++11
cu_compile_flags := -m64 -g -O0 -std=c++11
# 对头文件, 库文件,目标统一增加 -I,-L-l
rpath := $(foreach item,$(link_librarys),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))
# 合并选项
# 合并完选项后就可以给到编译方式里面去了
cpp_compile_flags += $(include_paths)
cu_compile_flags += $(include_paths)
link_flags := $(rpath) $(library_paths) $(link_librarys)
# makefile中定义cpp的编译方式
# $@:代表规则中的目标文件(生成项)
# $<:代表规则中的第一个依赖文件
# $^:代表规则中的所有依赖文件,以空格分隔
# $?:代表规则中所有比目标文件更新的依赖文件,以空格分隔
# 定义cpp文件的编译方式
# @echo Compile $< 输出正在编译的源文件的名称
objs/%.o : src/%.cpp
@mkdir -p $(dir $@)
@echo Compile $<
@g++ -c $^ -o $@ $(cpp_compile_flags)
# 定义.cu文件的编译方式
objs/%.cuo : src/%.cu
@mkdir -p $(dir $@)
@echo Compile $<
@nvcc -c $^ -o $@ $(cu_compile_flags)
# 定义workspace/pro文件的编译
$(workspace)/$(binary) : $(cpp_objs) $(cu_objs)
@mkdir -p $(dir $@)
@echo Link $^
@g++ $^ -o $@ $(link_flags) -L./objs
# 定义pro快捷编译指令,这里只发生编译,不执行
# 快捷指令就是make pro
pro : $(workspace)/$(binary)
# 定义指令并且执行的指令,并且执行目录切换到workspace下
run : pro
@cd $(workspace) && ./$(binary)
debug :
@echo $(cpp_objs)
@echo $(cu_objs)
clean :
@rm -rf objs $(workspace)/$(binary)
# 指定伪标签,作为指令
.PHONY : clean debug run pro
超全面和详细的一个makefil工程,值得学习借鉴
cc := g++
name := pro
workdir := workspace
srcdir := src
objdir := objs
stdcpp := c++11
cuda_home := /home/yuanwushui/anaconda3/lib/python3.9/site-packages/trtpy/trt8cuda112cudnn8
syslib := /home/yuanwushui/anaconda3/lib/python3.9/site-packages/trtpy/lib
cpp_pkg := /home/yuanwushui/anaconda3/lib/python3.9/site-packages/trtpy/cpp-packages #opencv4.2
cuda_arch :=
nvcc := $(cuda_home)/bin/nvcc -ccbin=$(cc)
# 定义cpp的路径查找和依赖项mk文件
cpp_srcs := $(shell find $(srcdir) -name "*.cpp")
cpp_objs := $(cpp_srcs:.cpp=.cpp.o)
cpp_objs := $(cpp_objs:$(srcdir)/%=$(objdir)/%)
cpp_mk := $(cpp_objs:.cpp.o=.cpp.mk)
# 定义cu文件的路径查找和依赖项mk文件
cu_srcs := $(shell find $(srcdir) -name "*.cu")
cu_objs := $(cu_srcs:.cu=.cu.o)
cu_objs := $(cu_objs:$(srcdir)/%=$(objdir)/%)
cu_mk := $(cu_objs:.cu.o=.cu.mk)
# 定义opencv和cuda需要用到的库文件
link_cuda := cudart cudnn
link_trtpro :=
link_tensorRT := nvinfer nvinfer_plugin
link_opencv := opencv_core opencv_imgproc opencv_imgcodecs opencv_videoio opencv_video opencv_highgui
link_sys := stdc++ dl protobuf
link_librarys := $(link_cuda) $(link_tensorRT) $(link_sys) $(link_opencv)
# 定义头文件路径,请注意斜杠后边不能有空格
# 只需要写路径,不需要写-I
include_paths := src \
$(cuda_home)/include/cuda \
$(cuda_home)/include/tensorRT \
$(cpp_pkg)/opencv4.2/include \
$(cuda_home)/include/protobuf \
include
# 定义库文件路径,只需要写路径,不需要写-L
library_paths := $(cuda_home)/lib64 $(syslib) $(cpp_pkg)/opencv4.2/lib
# 把library path给拼接为一个字符串,例如a b c => a:b:c
# 然后使得LD_LIBRARY_PATH=a:b:c
empty :=
library_path_export := $(subst $(empty) $(empty),:,$(library_paths))
# 把库路径和头文件路径拼接起来成一个,批量自动加-I、-L、-l
run_paths := $(foreach item,$(library_paths),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))
# 如果是其他显卡,请修改-gencode=arch=compute_75,code=sm_75为对应显卡的能力
# 显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
# 如果是 jetson nano,提示找不到-m64指令,请删掉 -m64选项。不影响结果
cpp_compile_flags := -std=$(stdcpp) -w -g -O0 -m64 -fPIC -fopenmp -pthread
cu_compile_flags := -std=$(stdcpp) -w -g -O0 -m64 $(cuda_arch) -Xcompiler "$(cpp_compile_flags)"
link_flags := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'
cpp_compile_flags += $(include_paths)
cu_compile_flags += $(include_paths)
link_flags += $(library_paths) $(link_librarys) $(run_paths)
# 如果头文件修改了,这里的指令可以让他自动编译依赖的cpp或者cu文件
ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endif
$(name) : $(workdir)/$(name)
all : $(name)
run : $(name)
@cd $(workdir) && ./$(name) $(run_args)
$(workdir)/$(name) : $(cpp_objs) $(cu_objs)
@echo Link $@
@mkdir -p $(dir $@)
@$(cc) $^ -o $@ $(link_flags)
$(objdir)/%.cpp.o : $(srcdir)/%.cpp
@echo Compile CXX $<
@mkdir -p $(dir $@)
@$(cc) -c $< -o $@ $(cpp_compile_flags)
$(objdir)/%.cu.o : $(srcdir)/%.cu
@echo Compile CUDA $<
@mkdir -p $(dir $@)
@$(nvcc) -c $< -o $@ $(cu_compile_flags)
# 编译cpp依赖项,生成mk文件
$(objdir)/%.cpp.mk : $(srcdir)/%.cpp
@echo Compile depends C++ $<
@mkdir -p $(dir $@)
@$(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)
# 编译cu文件的依赖项,生成cumk文件
$(objdir)/%.cu.mk : $(srcdir)/%.cu
@echo Compile depends CUDA $<
@mkdir -p $(dir $@)
@$(nvcc) -M $< -MF $@ -MT $(@:.cu.mk=.cu.o) $(cu_compile_flags)
# 定义清理指令
clean :
@rm -rf $(objdir) $(workdir)/$(name) $(workdir)/*.trtmodel $(workdir)/*.onnx
@rm -rf $(workdir)/image-draw.jpg $(workdir)/input-image.jpg $(workdir)/pytorch.jpg
# 防止符号被当做文件
.PHONY : clean run $(name)
# 导出依赖库路径,使得能够运行起来
export LD_LIBRARY_PATH:=$(library_path_export)