# # 基础配置用户根据项目修改这部分即可 # # 生成的动态库名称 TARGET : libipapply.so # 源文件目录默认当前目录多个目录用空格分隔 SRC_DIRS : . # 头文件搜索路径通用路径平台特有路径在下方配置 INCLUDES : . INCLUDES ../include INCLUDES $(LIB_ROOT)/include # 库文件搜索路径通用路径平台特有路径在下方配置 LIBPATH : . LIBPATH ./lib LIBPATH $(LIB_ROOT)/lib # 清理命令 RM : rm -f # # 平台选择取消注释你需要编译的平台只能选一个 # # PLATFORM : X86_64 # PC x86_64 平台 # PLATFORM : ARM64_GENERIC # 通用ARM64平台 PLATFORM : RK3588 # 瑞芯微RK3588平台 # PLATFORM : ARM64_RDBI # 自定义ARM64平台 # # 分平台差异化配置根据需要修改 # ifeq ($(PLATFORM), X86_64) # 编译器 CC : gcc CXX : g AR : ar STRIP : strip # 编译选项 CFLAGS : -g -O1 -Wall -marchx86-64 -stdgnu99 CPPFLAGS : -g -O1 -Wall -marchx86-64 -stdc11 CPPFLAGS -D DEV_TYP_FT # 自定义宏定义 # Boost库路径x86_64默认系统路径 BOOST_ROOT : /usr/local INCLUDES $(BOOST_ROOT)/include LIBPATH $(BOOST_ROOT)/lib # 链接库静态链接boost_system动态链接系统库 LIBS : -Wl,-Bstatic -lboost_system -Wl,-Bdynamic -lpthread -lm -ldl -lrt else ifeq ($(PLATFORM), ARM64_GENERIC) # 通用ARM64交叉编译器 CC : aarch64-linux-gnu-gcc CXX : aarch64-linux-gnu-g AR : aarch64-linux-gnu-ar STRIP : aarch64-linux-gnu-strip CFLAGS : -g -O3 -Wall -stdgnu99 CPPFLAGS : -g -O3 -Wall -stdc11 # ARM64 Boost库路径 BOOST_ROOT : /usr/local/boost_arm64_none INCLUDES $(BOOST_ROOT)/include LIBPATH $(BOOST_ROOT)/lib LIBS : -Wl,-Bstatic -lboost_system -Wl,-Bdynamic -lpthread -lm -ldl -lrt else ifeq ($(PLATFORM), RK3588) # 瑞芯微RK3588专用交叉编译器 CC : aarch64-rockchip1031-linux-gnu-gcc CXX : aarch64-rockchip1031-linux-gnu-g AR : aarch64-rockchip1031-linux-gnu-ar STRIP : aarch64-rockchip1031-linux-gnu-strip CFLAGS : -g -O3 -Wall -stdgnu99 CPPFLAGS : -g -O3 -Wall -stdc11 BOOST_ROOT : /usr/local/boost_arm64_none INCLUDES $(BOOST_ROOT)/include LIBPATH $(BOOST_ROOT)/lib LIBS : -Wl,-Bstatic -lboost_system -Wl,-Bdynamic -lpthread -lm -ldl -lrt else ifeq ($(PLATFORM), ARM64_RDBI) # 自定义ARM64平台配置 CC : aarch64-rockchip1031-linux-gnu-gcc CXX : aarch64-rockchip1031-linux-gnu-g AR : aarch64-rockchip1031-linux-gnu-ar STRIP : aarch64-rockchip1031-linux-gnu-strip CFLAGS : -g -O3 -Wall -stdgnu99 CPPFLAGS : -g -O3 -Wall -stdc11 BOOST_ROOT : /home/rdci/usr1/tools/boost/boost_arm64_none INCLUDES $(BOOST_ROOT)/include LIBPATH $(BOOST_ROOT)/lib LIBS : -Wl,-Bstatic -lboost_system -Wl,-Bdynamic -lpthread -lm -ldl -lrt endif # # 通用编译选项所有平台共用一般无需修改 # # 添加头文件路径前缀 -I CPPFLAGS $(addprefix -I,$(INCLUDES)) # 自动生成依赖文件(.d) CPPFLAGS -MMD # 生成位置无关代码动态库必须 CPPFLAGS -fPIC # 隐藏内部符号减小库体积 CPPFLAGS -fvisibilityhidden # 链接选项生成动态库 LDFLAGS : -shared # # 自动查找源文件和生成目标文件无需修改 # # 查找所有.c/.cpp/.cc源文件 CSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) CPPSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) CCSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cc)) # 生成对应的目标文件(.o) COBJS : $(patsubst %.c,%.o,$(CSRCS)) CPPOBJS : $(patsubst %.cpp,%.o,$(CPPSRCS)) CCOBJS : $(patsubst %.cc,%.o,$(CCSRCS)) # 生成对应的依赖文件(.d) CDEPS : $(patsubst %.o,%.d,$(COBJS)) CPPDEPS : $(patsubst %.o,%.d,$(CPPOBJS)) CCDEPS : $(patsubst %.o,%.d,$(CCOBJS)) # # 伪目标定义无需修改 # .PHONY: all deps objs clean rebuild # 默认目标生成动态库 all: $(TARGET) # 生成依赖文件 deps: $(CDEPS) $(CPPDEPS) $(CCDEPS) $(CXX) $(CPPFLAGS) -MM $(CSRCS) $(CPPSRCS) $(CCSRCS) # 生成目标文件 objs: $(COBJS) $(CPPOBJS) $(CCOBJS) # 清理中间文件和目标文件 clean: $(RM) $(COBJS) $(CPPOBJS) $(CCOBJS) $(RM) $(CDEPS) $(CPPDEPS) $(CCDEPS) $(RM) $(TARGET) # 重新编译 rebuild: clean all # # 最终链接规则无需修改 # # 包含自动生成的依赖文件 -include $(CDEPS) $(CPPDEPS) $(CCDEPS) # 链接生成动态库并strip去掉调试信息 $(TARGET): $(COBJS) $(CPPOBJS) $(CCOBJS) $(CXX) $(CPPFLAGS) $(LDFLAGS) -o $ $^ $(addprefix -L,$(LIBPATH)) $(LIBS) $(STRIP) $ # 编译完成后自动清理中间文件可选注释掉保留.o和.d文件加速增量编译 $(RM) $(COBJS) $(CPPOBJS) $(CCOBJS) $(RM) $(CDEPS) $(CPPDEPS) $(CCDEPS)预备知识Makefile本质是一个**“自动化编译说明书”**它解决的核心问题是当你有几十上百个C/C文件时不用手动敲几十行g xxx.cpp yyy.cpp -o xxx -Ixxx -Lxxx -lxxx只要敲一个make它就会自动帮你完成所有编译工作。它的核心逻辑只有3条目标我要生成什么文件比如libipapply.so依赖生成这个文件需要哪些原材料比如所有的.o目标文件命令用什么命令把原材料加工成目标第一部分基础配置你唯一需要经常改的部分# # 基础配置用户根据项目修改这部分即可 # 这是注释#后面的所有内容Makefile都会忽略只是给人看的说明文字。# 生成的动态库名称 TARGET : libipapply.so✅语法变量名 : 值—— 定义一个变量把右边的值赋给左边的变量。✅作用告诉Makefile我们最终要生成的文件名叫libipapply.so。✅什么时候改当你要生成别的名字的动态库时比如改成libmyutils.so。 小知识Linux下动态库的命名规范是libxxx.so这样别人才能用-lxxx链接它。# 源文件目录默认当前目录多个目录用空格分隔 SRC_DIRS : .✅语法.代表当前目录。✅作用告诉Makefile你的所有C/C源文件.c/.cpp/.cc都放在哪些目录里。✅什么时候改如果你的源文件放在src、utils等子目录里就改成SRC_DIRS : . src utils。# 头文件搜索路径通用路径平台特有路径在下方配置 INCLUDES : . INCLUDES ../include INCLUDES $(LIB_ROOT)/include✅语法给已经存在的变量追加内容$(变量名)引用一个变量的值这里$(LIB_ROOT)就是引用LIB_ROOT这个变量的值✅作用告诉C/C编译器当你在代码里写#include xxx.h时去哪些目录里找这个头文件。✅什么时候改当你新增了一个头文件目录时就加一行INCLUDES 你的头文件目录。 小知识编译器默认只会在当前目录找头文件不在当前目录的必须在这里告诉它。# 库文件搜索路径通用路径平台特有路径在下方配置 LIBPATH : . LIBPATH ./lib LIBPATH $(LIB_ROOT)/lib✅作用告诉链接器当你要链接某个库比如libboost_system.so时去哪些目录里找这个库文件。✅什么时候改当你新增了一个存放第三方库的目录时就加一行LIBPATH 你的库文件目录。# 清理命令 RM : rm -f✅作用定义一个删除文件的命令变量rm -f是Linux下强制删除文件的命令不会提示也不会因为文件不存在而报错。✅什么时候改几乎不用改这是标准写法。第二部分平台选择编译前必须选对# # 平台选择取消注释你需要编译的平台只能选一个 # # PLATFORM : X86_64 # PC x86_64 平台 # PLATFORM : ARM64_GENERIC # 通用ARM64平台 PLATFORM : RK3588 # 瑞芯微RK3588平台 # PLATFORM : ARM64_RDBI # 自定义ARM64平台✅语法这里的#是注释掉一行代码去掉#就是让这行代码生效。✅作用告诉Makefile你要把代码编译成能在哪个CPU架构上运行的程序。✅为什么要选平台你的电脑是x86_64架构Intel/AMD CPU手机和嵌入式板子是ARM64架构不同架构的CPU指令集完全不一样所以需要用不同的编译器✅使用规则只能取消一个平台的注释不能同时选多个。✅什么时候改想在自己电脑上编译调试 → 取消PLATFORM : X86_64的注释想编译给瑞芯微RK3588板子用 → 取消PLATFORM : RK3588的注释第三部分分平台差异化配置一般不用改# # 分平台差异化配置根据需要修改 # ifeq ($(PLATFORM), X86_64)✅语法ifeq ($(变量名), 比较值)—— 条件判断如果变量的值等于比较值就执行下面的代码直到遇到else ifeq或endif。✅作用根据你上面选的平台自动加载对应的编译器和配置。# 编译器 CC : gcc CXX : g AR : ar STRIP : strip✅作用定义4个工具变量CCC语言编译器编译.c文件用CXXC语言编译器编译.cpp/.cc文件用AR静态库打包工具这个项目生成动态库暂时用不上STRIP程序瘦身工具去掉调试信息让生成的库体积变小✅为什么不同平台不一样PC上用系统自带的gcc/g交叉编译ARM64时要用专门的交叉编译器名字很长的那个它能在PC上编译出能在ARM板子上跑的程序# 编译选项 CFLAGS : -g -O1 -Wall -marchx86-64 -stdgnu99 CPPFLAGS : -g -O1 -Wall -marchx86-64 -stdc11✅作用给编译器传递参数告诉它怎么编译代码。✅每个参数的含义参数作用-g给生成的文件加调试信息这样你才能用GDB调试程序-O1代码优化级别O0不优化调试用O1轻度优化O3最高优化发布用-Wall显示所有编译警告强烈建议保留能帮你提前发现90%的低级bug-marchx86-64生成针对x86-64架构优化的代码-stdgnu99用GNU扩展的C99标准编译C代码-stdc11用C11标准编译C代码CPPFLAGS -D DEV_TYP_FT # 自定义宏定义✅语法-D 宏名—— 在代码中定义一个宏。✅作用你可以在代码里写#ifdef DEV_TYP_FT然后写只有在这个平台下才会执行的代码。✅什么时候改当你需要给某个平台加专属宏定义时。# Boost库路径x86_64默认系统路径 BOOST_ROOT : /usr/local INCLUDES $(BOOST_ROOT)/include LIBPATH $(BOOST_ROOT)/lib✅作用定义第三方库Boost的根目录然后把它的头文件和库文件路径加到搜索路径里。✅什么时候改当你的Boost库安装在别的目录时修改BOOST_ROOT的值。# 链接库静态链接boost_system动态链接系统库 LIBS : -Wl,-Bstatic -lboost_system -Wl,-Bdynamic -lpthread -lm -ldl -lrt✅作用告诉链接器要链接哪些库文件。✅语法解释-lxxx链接名为libxxx.so动态库或libxxx.a静态库的库-Wl,-Bstatic告诉链接器后面的库用静态链接把库的代码直接打包到你的程序里-Wl,-Bdynamic告诉链接器后面的库用动态链接程序运行时才加载库文件✅为什么这么写静态链接Boost库这样你的程序在板子上运行时不需要再安装Boost库动态链接系统库pthread、m等因为系统库是所有程序共用的。else ifeq ($(PLATFORM), ARM64_GENERIC) # ... 通用ARM64平台的配置 ... else ifeq ($(PLATFORM), RK3588) # ... RK3588平台的配置 ... else ifeq ($(PLATFORM), ARM64_RDBI) # ... 自定义ARM64平台的配置 ... endif✅作用这几个分支和上面的X86_64分支结构完全一样只是编译器路径、Boost库路径和优化级别不同。✅什么时候改当你新增一个平台时复制一个分支修改对应的编译器和库路径即可。第四部分通用编译选项所有平台共用绝对不要改# # 通用编译选项所有平台共用一般无需修改 # # 添加头文件路径前缀 -I CPPFLAGS $(addprefix -I,$(INCLUDES))✅语法$(addprefix 前缀, 列表)—— Makefile的内置函数给列表里的每个元素都加上指定的前缀。✅作用把INCLUDES里的每个路径前面都加上-I变成编译器能识别的头文件路径格式。比如INCLUDES . ../include会变成-I. -I../include。# 自动生成依赖文件(.d) CPPFLAGS -MMD✅作用告诉编译器编译每个源文件时自动生成一个对应的.d依赖文件。✅为什么需要这个依赖文件里记录了这个源文件包含了哪些头文件。当你修改了某个头文件时Makefile会自动重新编译所有包含这个头文件的源文件不用你手动全量编译。# 生成位置无关代码动态库必须 CPPFLAGS -fPIC✅作用生成位置无关代码Position Independent Code。✅为什么动态库必须要有这个动态库是在程序运行时才加载到内存的加载的地址是不固定的。位置无关代码可以在任何内存地址运行不需要修改。如果不加这个选项生成的动态库会报错。# 隐藏内部符号减小库体积 CPPFLAGS -fvisibilityhidden✅作用把库内部的函数和变量都隐藏起来只暴露你明确指定要导出的接口。✅好处减小库的体积提高运行速度防止别人调用你不想暴露的内部函数。# 链接选项生成动态库 LDFLAGS : -shared✅作用告诉链接器我们要生成的是动态库而不是可执行程序。第五部分自动查找源文件和生成目标文件绝对不要改# # 自动查找源文件和生成目标文件无需修改 # # 查找所有.c/.cpp/.cc源文件 CSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) CPPSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) CCSRCS : $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cc))✅语法$(foreach 变量, 列表, 执行语句)遍历列表里的每个元素执行语句$(wildcard 模式)匹配所有符合模式的文件✅作用自动遍历SRC_DIRS里的所有目录找到所有后缀为.c、.cpp、.cc的源文件分别存到CSRCS、CPPSRCS、CCSRCS变量里。✅好处你不用手动一个个把源文件名加到Makefile里新增或删除源文件时Makefile会自动识别。# 生成对应的目标文件(.o) COBJS : $(patsubst %.c,%.o,$(CSRCS)) CPPOBJS : $(patsubst %.cpp,%.o,$(CPPSRCS)) CCOBJS : $(patsubst %.cc,%.o,$(CCSRCS))✅语法$(patsubst 旧模式, 新模式, 列表)模式替换函数把列表里所有符合旧模式的元素替换成新模式。✅作用把每个源文件的后缀改成.o得到对应的目标文件列表。比如main.c会变成main.outils.cpp会变成utils.o。 小知识编译过程是先把每个源文件编译成.o目标文件再把所有.o文件链接成最终的动态库或可执行程序。# 生成对应的依赖文件(.d) CDEPS : $(patsubst %.o,%.d,$(COBJS)) CPPDEPS : $(patsubst %.o,%.d,$(CPPOBJS)) CCDEPS : $(patsubst %.o,%.d,$(CCOBJS))✅作用把每个目标文件的后缀改成.d得到对应的依赖文件列表。第六部分伪目标定义绝对不要改# # 伪目标定义无需修改 # .PHONY: all deps objs clean rebuild✅语法.PHONY: 目标名列表—— 声明这些目标是伪目标。✅什么是伪目标这些目标不是真正的文件只是一个命令的名字。比如clean不是一个叫clean的文件只是执行删除命令的名字。✅为什么要声明如果你的目录里刚好有一个叫clean的文件Makefile会以为clean目标已经是最新的不会执行删除命令。声明为伪目标后Makefile就不会检查文件是否存在每次都会执行对应的命令。# 默认目标生成动态库 all: $(TARGET)✅作用定义默认目标。当你只敲make不加任何参数时Makefile会自动执行这个目标。✅依赖关系all目标依赖于$(TARGET)也就是libipapply.so所以执行make会先生成libipapply.so。# 生成依赖文件 deps: $(CDEPS) $(CPPDEPS) $(CCDEPS) $(CXX) $(CPPFLAGS) -MM $(CSRCS) $(CPPSRCS) $(CCSRCS)✅作用只生成所有依赖文件不编译代码。一般用不上。# 生成目标文件 objs: $(COBJS) $(CPPOBJS) $(CCOBJS)✅作用只把所有源文件编译成.o目标文件不链接成最终的动态库。一般用不上。# 清理中间文件和目标文件 clean: $(RM) $(COBJS) $(CPPOBJS) $(CCOBJS) $(RM) $(CDEPS) $(CPPDEPS) $(CCDEPS) $(RM) $(TARGET)✅语法命令前面的表示不显示命令本身只显示命令的输出。✅作用删除所有编译生成的中间文件.o和.d和最终的动态库文件。✅什么时候用编译报错想重新编译整个项目时切换平台编译前比如从X86切换到ARM64# 重新编译 rebuild: clean all✅作用先执行clean清理所有文件再执行all重新编译整个项目。✅什么时候用解决一些奇怪的编译问题比如修改了头文件但Makefile没有重新编译。第七部分最终链接规则绝对不要改# # 最终链接规则无需修改 # # 包含自动生成的依赖文件 -include $(CDEPS) $(CPPDEPS) $(CCDEPS)✅语法-include 文件名列表—— 包含指定的文件如果文件不存在也不报错。✅作用把前面自动生成的.d依赖文件包含进来这样Makefile就能知道每个源文件依赖哪些头文件了。# 链接生成动态库并strip去掉调试信息 $(TARGET): $(COBJS) $(CPPOBJS) $(CCOBJS) $(CXX) $(CPPFLAGS) $(LDFLAGS) -o $ $^ $(addprefix -L,$(LIBPATH)) $(LIBS) $(STRIP) $ # 编译完成后自动清理中间文件可选注释掉保留.o和.d文件加速增量编译 $(RM) $(COBJS) $(CPPOBJS) $(CCOBJS) $(RM) $(CDEPS) $(CPPDEPS) $(CCDEPS)✅这是整个Makefile最核心的规则我们逐行拆解依赖关系$(TARGET): $(COBJS) $(CPPOBJS) $(CCOBJS) 生成最终的动态库libipapply.so需要所有的.o目标文件。链接命令$(CXX) $(CPPFLAGS) $(LDFLAGS) -o $ $^ $(addprefix -L,$(LIBPATH)) $(LIBS)✅自动变量$代表目标文件也就是libipapply.so$^代表所有依赖文件也就是所有的.o目标文件✅作用用C编译器把所有.o文件链接成最终的动态库同时链接指定的库文件。瘦身命令$(STRIP) $ 用strip工具去掉动态库里的调试信息让库的体积变小一般能减小70%以上。清理命令最后两行是编译完成后自动删除中间文件。✅建议把这两行注释掉这样下次编译时只有修改过的源文件会被重新编译编译速度会快很多。最后整个编译流程总结当你敲make命令时Makefile会按以下步骤执行读取你选择的平台加载对应的编译器和配置自动查找所有源文件生成对应的目标文件和依赖文件列表包含所有依赖文件建立完整的依赖关系逐个编译每个源文件生成对应的.o目标文件把所有.o文件链接成最终的libipapply.so动态库用strip给动态库瘦身可选删除中间文件小白必背3个命令make编译生成动态库make clean清理所有编译产物make rebuild重新编译整个项目