Makefile实战

Makefile实战

前言

学习完了杜老师推荐的Makefile教程视频,链接。来个实战先。

针对https://github.com/shouxieai/infer仓库中的Makefile进行分析和修改,完成该项目在Jetson nano上的编译和运行。

1.介绍

infer框架是杜老师最近推出的全新tensorrt封装,轻易继承各类任务,其优点有

  • 轻易实现各类任务的生产者消费者模型,并进行高性能推理
  • 没有复杂的封装,彻底解开耦合!

除去多余的文件,完整的目录结构如下

  • src
    • 存放源文件,包括6个文件,分别为cpm.hppinfer.hpp以及yolo.hpp三个头文件;infer.cuyolo.cu两个CUDA文件;main.cpp一个源文件
  • workspace
    • inference
      • 存放待推理的图片
    • build.sh,脚本文件,利用trtexec工具将ONNX模型编译生成engine
    • v8trans.py,python文件,用于在yolov8的onnx模型中添加transpose节点
    • yolov8n.transd.onnx和yolov8n-seg.b1.transd.onnx为yolov8导出的检测和分割模型
  • Makefile

说明

  • 我们尽量只关注Makefile文件中的内容,先分析原Makefile内容然后修改满足我们的需求

  • 文件虽然少但是包含的内容多,具体包括C++和CUDA的混合编程,以及tensorRT和OpenCV等库文件的链接,还是非常值得学习的。

  • 本次使用的设备是Jetson nano嵌入式

  • 对于该项目感兴趣的可以查看如何高效使用TensorRT视频

  • 对于该项目应用在Jetson nano嵌入式感兴趣的可以查看Jetson nano部署YOLOv8

2.Makefile文件分析

首先来看下该仓库的Makefile文件具体内容:

cc        := g++
nvcc      = /root/.kiwi/lib/cuda-11.8/bin/nvcc

include_paths := src        \
			/root/.kiwi/lib/opencv4.2/include \
			/root/.kiwi/lib/TensorRT-8.5.3.1-cuda11x/include \
			/root/.kiwi/lib/cuda-11.8/include \
			/root/.kiwi/lib/cudnn8.6.0.163-cuda11/lib

library_paths := /root/.kiwi/lib/opencv4.2/lib \
			/root/.kiwi/lib/TensorRT-8.5.3.1-cuda11x/lib \
			/root/.kiwi/lib/cuda-11.8/lib64 \
 			/root/.kiwi/lib/cudnn8.6.0.163-cuda11/lib

link_librarys := opencv_core opencv_imgproc opencv_videoio opencv_imgcodecs \
			nvinfer nvinfer_plugin nvonnxparser \
			cuda cublas cudart cudnn \
			stdc++ dl

cppstrict := -Wall -Werror -Wextra -Wno-deprecated-declarations -Wno-unused-parameter
custrict  := -Werror=all-warnings
cpp_compile_flags := -std=c++11 -fPIC -g -fopenmp $(cppstrict) -O0
cu_compile_flags  := -std=c++11 $(custrict) -O0 -Xcompiler "$(cpp_compile_flags)"
link_flags        := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'

include /root/.kiwi/lib/cumk/inc

这个Makefile文件的内容并不非常完整,只定义了一些变量和搜索路径相关,后续修改还需要添加编译和链接的内容。以下是对每个部分的详细分析

1.定义编译器和nvcc路径

cc        := g++
nvcc      = /root/.kiwi/lib/cuda-11.8/bin/nvcc

其中:

  • cc是用于编译C++代码的GNU C++编译器;
  • nvcc是用于编译CUDA代码的NVIDIA CUDA编译器

2.定义include路径、库路径和链接的库名

include_paths := src        \
			/root/.kiwi/lib/opencv4.2/include \
			/root/.kiwi/lib/TensorRT-8.5.3.1-cuda11x/include \
			/root/.kiwi/lib/cuda-11.8/include \
			/root/.kiwi/lib/cudnn8.6.0.163-cuda11/lib

library_paths := /root/.kiwi/lib/opencv4.2/lib \
			/root/.kiwi/lib/TensorRT-8.5.3.1-cuda11x/lib \
			/root/.kiwi/lib/cuda-11.8/lib64 \
 			/root/.kiwi/lib/cudnn8.6.0.163-cuda11/lib

link_librarys := opencv_core opencv_imgproc opencv_videoio opencv_imgcodecs \
			nvinfer nvinfer_plugin nvonnxparser \
			cuda cublas cudart cudnn \
			stdc++ dl

其中:

  • include_paths定义了需要编译的代码的头文件所在路径;
  • library_paths定义了库文件的路径;
  • link_librarys定义了需要链接的库名。

3.定义编译选项

cppstrict := -Wall -Werror -Wextra -Wno-deprecated-declarations -Wno-unused-parameter
custrict  := -Werror=all-warnings
cpp_compile_flags := -std=c++11 -fPIC -g -fopenmp $(cppstrict) -O0
cu_compile_flags  := -std=c++11 $(custrict) -O0 -Xcompiler "$(cpp_compile_flags)"
link_flags        := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'

其中,

  • cppstrict是用于编译C++代码的编译警告选项;
  • custrict是用于编译CUDA代码的编译警告选项;
  • cpp_compile_flags是用于编译C++代码的编译选项,包括C++11标准、位置独立代码、调试信息、OpenMP多线程编程等;
  • cu_compile_flags是用于编译CUDA代码的编译选项,包括C++11标准、位置独立代码、关闭所有警告、调试信息等;
  • link_flags是用于链接库的选项,包括多线程编程和动态链接库路径

4.包含一个第三方库

include /root/.kiwi/lib/cumk/inc

3.Makefile文件修改

修改的地方有点多,下面具体来分析

3.1 定义编译器

cc        := g++
nvcc      := /usr/local/cuda-10.2/bin/nvcc

其中,

  • cc := g++定义了C++编译器变量cc
  • nvcc := /usr/local/cuda-10.2/bin/nvcc定义了CUDA编译器变量nvcc

3.2 定义源文件和目标文件

cpp_srcs  := $(shell find src -name "*.cpp")
cpp_objs  := $(cpp_srcs:.cpp=.cpp.o)
cpp_objs  := $(cpp_objs:src/%=objs/%)
cpp_mk	  := $(cpp_objs:.cpp.o=.cpp.mk)

cu_srcs	  := $(shell find src -name "*.cu")
cu_objs   := $(cu_srcs:.cu=.cu.o)
cu_objs	  := $(cu_objs:src/%=objs/%)
cu_mk	  := $(cu_objs:.cu.o=.cu.mk)

其中,

  • cpp_srcs := $(shell find src -name "*.cpp")使用shell命令查找源代码src目录下的所有.cpp文件,并将结果保存到cpp_srcs变量中
  • cpp_objs := $(cpp_srcs:.cpp=.cpp.o)cpp_srcs中的每个.cpp文件中的.cpp替换为.cpp.o,为了将所有的.cpp文件编译成.o文件
  • cpp_objs := $(cpp_objs:src/%=objs/%)cpp_objs中所有的obj文件的路径前缀从src/替换为objs/,即将所有的.o文件放在objs目录下
  • cpp_mk := $(cpp_objs:.cpp.o=.cpp.mk)将所有的.cpp.o文件替换为.cpp.mk,即为每个.cpp源文件生成一个依赖文件
  • cu_srcscu_objscu_mk同理,只是操作的是src下面的所有.cu文件

说明

  • $(cpp_srcs:.cpp=.cpp.o)这是make中一种字符串语法,语法结构如下

    $(VAR:a=b)
    

    其中,VAR为变量名,ab均为字符串,表示将VAR中所有以a结尾的字符串替换为b结尾的字符串。相比于patsubst函数,这种语法更加简洁,适用于只需要进行简单字符串替换的情况。

  • $(cpp_objs:.cpp.o=.cpp.mk)其中的.mk文件用来记录源文件和头文件的依赖关系。

    • 在C/C++项目中,一个.cpp文件往往包含了多个.h头文件,这些头文件的修改也会影响到.cpp文件的编译,因此正常情况下需要在头文件被修改时重新编译.cpp文件。
    • .mk文件记录了每个源文件所依赖的头文件,以及头文件的修改时间,从而实现自动化依赖关系的生成。因此在修改头文件重新编译时,只需要重新编译与头文件相关的源文件即可。这可以大大减少编译时间,尤其在大型项目中尤为明显,如果没有.mk文件,每次重新编译时都需要重新编译整个项目,这将非常耗费时间和资源
    • 在Makefile中使用-include命令可以将.mk文件包含进来,实现了Makefile的自动化构建过程

3.3 定义头文件路径、库文件路径和库名称

include_paths := src        \
			/usr/include/opencv4 \
			/usr/include/aarch64-linux-gnu \
			/usr/local/cuda-10.2/include

library_paths := /usr/lib/aarch64-linux-gnu \
				 /usr/local/cuda-10.2/lib64

link_librarys := opencv_core opencv_highgui opencv_imgproc opencv_videoio opencv_imgcodecs \
				 nvinfer nvinfer_plugin nvonnxparser \
				 cuda cublas cudart cudnn \
				 stdc++ dl

其中,

  • include_paths定义了所有头文件所在的路径,主要包含以下几方面内容

    • src文件下的.hpp文件
    • opencv头文件
    • tensorRT、cudnn头文件
    • cuda头文件
  • library_paths定义了所有库文件所在的路径,主要包含以下几方面的内容

    • opencv、tensorRT、cudnn库文件
    • cuda库文件
  • link_librarys定义了需要链接的所有库文件,主要包含以下几方面的内容

    • opencv库文件:opencv_core、opencv_highgui、opencv_imgproc、opencv_videoio、opencv_imgcodecs
    • tensorRT库文件:nvinfer、nvinfer_plugin、nvonnxparser
    • cuda库文件:cuda、cublas、cudart
    • cudnn库文件:cudnn
    • GNU C++标准库:stdc++;动态链接库:dl

3.4 定义导出和运行时路径

empty		  :=
export_path   := $(subst $(empty) $(empty),:,$(library_paths))

run_paths     := $(foreach item,$(library_paths),-Wl,-rpath=$(item))

其中,

  • export_path := $(subst $(empty) $(empty),:,$(library_paths))library_paths中的所有路径用冒号连接起来,用于在链接时指定动态库搜索路径
  • run_paths := $(foreach item,$(library_paths),-Wl,-rpath=$(item))library_paths中的每个路径添加上链接选项-Wl,-rpath,用于在链接时指定动态库搜索路径

说明

  • 以下是subst函数的语法结构,针对$(subst $(empty) $(empty),:,$(library_paths))这个语句来说<from>代表的是$(empty) $(empty)这一整体内容,$(empty)代表空字符串,不是空格,也没有其它任何字符。$(empty) $(empty)是为了产生一个空格的效果。

    $(subst <from>,<to>,<text>)
    
  • $(subst $(empty) $(empty),:,$(library_paths))的整体含义就是将其中的空格替换成冒号,以便链接器可以正确地查找动态库。例如,如果library_paths地值为:

    /usr/lib/aarch64-linux-gnu /usr/local/cuda-10.2/lib64
    

    那么以下几种情况对应的输出分别为:

    export_path   := $(subst $(empty) $(empty),:,$(library_paths))
    /usr/lib/aarch64-linux-gnu:/usr/local/cuda-10.2/lib64
    
    export_path1  := $(subst $(empty),:,$(library_paths))
    /usr/lib/aarch64-linux-gnu /usr/local/cuda-10.2/lib64:
    
    export_path2  := $(subst ,:,$(library_paths))
    /usr/lib/aarch64-linux-gnu /usr/local/cuda-10.2/lib64:
    

3.5 定义编译选项

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 := -std=c++11 -fPIC -w -g -pthread -fopenmp -O0
cu_compile_flags  := -std=c++11 -g -w -O0 -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)

其中,

  • include_paths是一个路径列表,通过foreach函数将其转换为-I开头的参数列表。这些参数用于指定头文件所在的路径。
  • library_paths是一个库路径列表,通过foreach函数将其转换为-L开头的参数列表。这些参数用于指定库文件所在的路径。
  • link_librarys是一个库文件列表,通过foreach函数将其转换为-l开头的参数列表。这些参数用于指定链接的库文件。
  • cpp_compile_flags是编译C++文件的编译参数列表,其中包括了``include_paths-fPIC(生成位置无关代码)、-w(忽略警告)、-g(生成调试信息)、-pthread(链接线程库)、-fopenmp(开启OpenMP多线程)和-O0`(关闭优化)等选项。
  • cu_compile_flags是编译CUDA文件的编译参数列表,同上,-Xcompile(指定传递给底层C++编译器的参数)
  • link_flags是链接参数列表,其中包括了library_pathslink_librarysrun_paths等选项。run_paths选项用于指定运行时的库路径,用于解决动态库加载问题。

说明

  • fPIC(Position Independent Code),它的用途是生成与位置无关的代码。在编译生成动态库时,这个参数可以确保代码中的全局符号表、重定位表等数据的位置独立于内存中的位置,从而允许多个进程在内存中共享同一份库代码。简单来说,fPIC是在编译生成动态库时必须加上的参数,以确保生成的动态库能够被不同的进程共享使用
  • 在CUDA和C++混合编程中,由于NVCC和g++编译器的编译选项有所不同,需要为它们分别指定不同的编译选项。NVCC编译器底层需要调用g++编译器,可以通过-Xcompile选项将参数传递给NVCC生成的C++编译器。这样,可以确保NVCC编译器和g++编译器都使用了相同的选项,从而确保了代码的正确性和一致性
  • -Wl,-rpath='$$ORIGIN'表示将rpath设置为$ORIGIN,在这里$ORIGIN是一个特殊字符,表示可执行文件所在目录的路径。双重美元符号$$是因为$ORIGIN在Shell中有特殊含义,因此需要转义。

3.6 添加头文件依赖

ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endif

说明

  • -include是Makefile中的内置函数,它的作用是将指定的文件包含到当前的Makefile中。上面的语句的作用是在执行make命令时,如果没有clean目标,就会包含(include)指定的文件即$(cpp_mk)$(cu_mk)。而这两个.mk文件包含了相应的源文件的依赖关系。这样在修改源文件时,make会自动判断哪些文件需要重新编译,以及哪些文件需要重新链接,从而提高了构建效率。
  • 如果不包含这个语句,可能导致make不能正确地识别源文件之间的依赖关系,或者需要程序编译所有文件,从而降低了构建效率。因此,在构建复杂项目时,最好包含这个语句确保构建的正确性和效率。

3.7 编译CUDA和C++程序

pro	   := workspace/pro
expath := library_path.txt

library_path.txt : 
	@echo LD_LIBRARY_PATH=$(export_path):"$$"LD_LIBRARY_PATH > $@

workspace/pro : $(cpp_objs) $(cu_objs)
	@echo Link $@
	@mkdir -p $(dir $@)
	@$(cc) $^ -o $@ $(link_flags)

objs/%.cpp.o : src/%.cpp
	@echo Compile CXX $<
	@mkdir -p $(dir $@)
	@$(cc) -c $< -o $@ $(cpp_compile_flags)

objs/%.cu.o : src/%.cu
	@echo Compile CUDA $<
	@mkdir -p $(dir $@)
	@$(nvcc) -c $< -o $@ $(cu_compile_flags)

objs/%.cpp.mk : src/%.cpp
	@echo Compile depends CXX $<
	@mkdir -p $(dir $@)
	@$(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)
	
objs/%.cu.mk : src/%.cu
	@echo Compile depends CUDA $<
	@mkdir -p $(dir $@)
	@$(nvcc) -M $< -MF $@ -MT $(@:.cu.mk=.cu.o) $(cu_compile_flags)

run : workspace/pro
	@cd workspace && ./pro

其中,

  • pro变量表示最终生成的可执行文件的路径
  • expath变量表示库文件路径的导出文件名
  • library_path.txt文件用于生成LD_LIBRARY_PATH环境变量所需的值,其内容是LD_LIBRARY_PATHexport_path的值
  • workspace/pro规则用于编译链接生成最终的可执行文件。其中包含了所有的C++和CUDA文件
  • objs/%.cpp.o规则和objs/%.cu.o规则分别用于编译C++和CUDA文件,生成目标文件
  • objs/%.cpp.mk规则和objs/%.cu.mk规则分别用于生成C++和CUDA文件的依赖关系描述文件
  • run规则用于运行生成的可执行文件

说明

  • objs/%.cpp.mk : src/%.cpp定义了一条规则,当在src目录下的.cpp发生变化时,会执行该规则生成对应的.cpp.mk文件
  • $(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)该命令会将根据源文件生成对应的依赖关系文件
    • -M:表示只输出每个源文件的依赖关系,不包括编译指令,例如-M src/main.cpp
    • -MF:表示将输出的依赖关系写入到指定的文件中,例如-MF objs/main.cpp.mk
    • -MF:表示制定目标文件,即make要生成的文件,例如-MT objs/main.cpp.o

3.8 其它

debug :
	@echo $(include_paths)
	@echo $(library_paths)
	@echo $(link_librarys)

clean : 
	@rm -rf library_path.txt
	@rm -rf objs workspace/pro
	@rm -rf workspace/Result.jpg

.PHONY : debug clean run

export LD_LIBRARY_PATH:=$(export_path):$(LD_LIBRARY_PATH)

其中,

  • clean规则用于删除生成的目标文件、库文件和可执行文件
  • export LD_LIBRARY_PATH语句用于导出LD_LIBRARY_PATH环境变量,使得运行时能够正确链接库文件。

4.完整Makefile

完整的Makefile示例如下:

cc        := g++
nvcc      := /usr/local/cuda-10.2/bin/nvcc

cpp_srcs  := $(shell find src -name "*.cpp")
cpp_objs  := $(cpp_srcs:.cpp=.cpp.o)
cpp_objs  := $(cpp_objs:src/%=objs/%)
cpp_mk	  := $(cpp_objs:.cpp.o=.cpp.mk)

cu_srcs	  := $(shell find src -name "*.cu")
cu_objs   := $(cu_srcs:.cu=.cu.o)
cu_objs	  := $(cu_objs:src/%=objs/%)
cu_mk	  := $(cu_objs:.cu.o=.cu.mk)

include_paths := src        \
			/usr/include/opencv4 \
			/usr/include/aarch64-linux-gnu \
			/usr/local/cuda-10.2/include

library_paths := /usr/lib/aarch64-linux-gnu \
				 /usr/local/cuda-10.2/lib64

link_librarys := opencv_core opencv_highgui opencv_imgproc opencv_videoio opencv_imgcodecs \
				 nvinfer nvinfer_plugin nvonnxparser \
				 cuda cublas cudart cudnn \
				 stdc++ dl

empty		  :=
export_path   := $(subst $(empty) $(empty),:,$(library_paths))

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))

cpp_compile_flags := -std=c++11 -fPIC -w -g -pthread -fopenmp -O0
cu_compile_flags  := -std=c++11 -g -w -O0 -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)

ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endif

pro	   := workspace/pro
expath := library_path.txt

library_path.txt : 
	@echo LD_LIBRARY_PATH=$(export_path):"$$"LD_LIBRARY_PATH > $@

workspace/pro : $(cpp_objs) $(cu_objs)
	@echo Link $@
	@mkdir -p $(dir $@)
	@$(cc) $^ -o $@ $(link_flags)

objs/%.cpp.o : src/%.cpp
	@echo Compile CXX $<
	@mkdir -p $(dir $@)
	@$(cc) -c $< -o $@ $(cpp_compile_flags)

objs/%.cu.o : src/%.cu
	@echo Compile CUDA $<
	@mkdir -p $(dir $@)
	@$(nvcc) -c $< -o $@ $(cu_compile_flags)

objs/%.cpp.mk : src/%.cpp
	@echo Compile depends CXX $<
	@mkdir -p $(dir $@)
	@$(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)
	
objs/%.cu.mk : src/%.cu
	@echo Compile depends CUDA $<
	@mkdir -p $(dir $@)
	@$(nvcc) -M $< -MF $@ -MT $(@:.cu.mk=.cu.o) $(cu_compile_flags)

run : workspace/pro
	@cd workspace && ./pro

debug :
	@echo $(include_paths)
	@echo $(library_paths)
	@echo $(link_librarys)

clean : 
	@rm -rf library_path.txt
	@rm -rf objs workspace/pro
	@rm -rf workspace/Result.jpg

.PHONY : debug clean run

export LD_LIBRARY_PATH:=$(export_path):$(LD_LIBRARY_PATH)

执行make run运行效果如下:

在这里插入图片描述

总结

本次实战选取开源仓库https://github.com/shouxieai/infer中的Makefile文件做了具体的分析和修改,在今后的工作中多看多写吧😄

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值