# ============================================== # 基础配置(用户根据项目修改这部分即可) # ============================================== # 生成的动态库名称 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 -march=x86-64 -std=gnu99 CPPFLAGS := -g -O1 -Wall -march=x86-64 -std=c++11 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 -std=gnu99 CPPFLAGS := -g -O3 -Wall -std=c++11 # 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 -std=gnu99 CPPFLAGS := -g -O3 -Wall -std=c++11 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 -std=gnu99 CPPFLAGS := -g -O3 -Wall -std=c++11 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 += -fvisibility=hidden # 链接选项:生成动态库 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个工具变量:
CC:C语言编译器(编译.c文件用)CXX:C++语言编译器(编译.cpp/.cc文件用)AR:静态库打包工具(这个项目生成动态库,暂时用不上)STRIP:程序瘦身工具(去掉调试信息,让生成的库体积变小)
✅为什么不同平台不一样:- PC上用系统自带的
gcc/g++ - 交叉编译ARM64时,要用专门的交叉编译器(名字很长的那个),它能在PC上编译出能在ARM板子上跑的程序
# 编译选项 CFLAGS := -g -O1 -Wall -march=x86-64 -std=gnu99 CPPFLAGS := -g -O1 -Wall -march=x86-64 -std=c++11✅作用:给编译器传递参数,告诉它怎么编译代码。
✅每个参数的含义:
| 参数 | 作用 |
|---|---|
-g | 给生成的文件加调试信息,这样你才能用GDB调试程序 |
-O1 | 代码优化级别(O0=不优化,调试用;O1=轻度优化;O3=最高优化,发布用) |
-Wall | 显示所有编译警告(强烈建议保留,能帮你提前发现90%的低级bug) |
-march=x86-64 | 生成针对x86-64架构优化的代码 |
-std=gnu99 | 用GNU扩展的C99标准编译C代码 |
-std=c++11 | 用C++11标准编译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 += -fvisibility=hidden✅作用:把库内部的函数和变量都隐藏起来,只暴露你明确指定要导出的接口。
✅好处:减小库的体积,提高运行速度,防止别人调用你不想暴露的内部函数。
# 链接选项:生成动态库 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.o,utils.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:重新编译整个项目