IMake:让构建回归Makefile本质,前所未有的简单体验
项目地址
- github: https://github.com/lengjingzju/cbuild-ng/tree/main/scripts/core
- gitee: https://gitee.com/lengjingzju/cbuild-ng/tree/main/scripts/core
kconfig地址
- github: https://github.com/lengjingzju/kconfig
- gitee: https://gitee.com/lengjingzju/kconfig
现代构建系统的困境与IMake的诞生
在当今软件开发领域,构建系统已成为项目成功的关键因素。从经典的Makefile到现代的Autotools、CMake和Meson,开发者们一直在寻找更高效、更灵活的构建工具。然而,这些工具往往伴随着陡峭的学习曲线和复杂的配置语法:
- Autotools 需要编写复杂的configure.ac和Makefile.am文件,使用M4宏语言,配置过程繁琐
- CMake 虽然功能强大,但需要学习专属的CMakeLists.txt语法,生成的Makefile难以理解和调试
- Meson 作为后起之秀,设计理念先进但仍需学习新的配置语言
这些工具在追求功能强大的同时,却远离了构建系统的本质——简单、直观和可控。正是在这样的背景下,IMake(Include Makefile)应运而生,它让构建回归Makefile的本质,同时提供了现代构建系统的高级特性。
IMake核心优势:简单而不失强大
IMake(Include Makefile)是一套基于纯Makefile实现的构建模板系统,它通过模块化的设计理念,将复杂的构建逻辑封装为可重用的模板。只需包含相应的模板文件并设置少量变量,您就能轻松完成从简单应用到复杂系统的构建配置。
核心优势
- 极简配置:告别复杂的configure.ac、CMakeLists.txt、meson.build,只需定义几个变量即可完成编译
- 原生Makefile体验:完全基于Makefile,无需学习新语法,现有知识完全适用
- menuconfig图形化配置:提供熟悉的Kconfig界面,轻松管理构建选项
- 多功能支持:同时支持本地编译和交叉编译、经典编译和Yocto编译,静态库、动态库、可执行文件、内核模块一网打尽
- 工具链配置:支持GCC和Clang,轻松切换编译环境
- 智能依赖处理:自动分析头文件依赖,探测环境配置自动重新编译,确保正确的构建顺序
- 编译选项预置:预置常用的可选编译选项:优化等级、安全增强、安全调试(sanitizer)、静态分析(analyzer)、性能分析(gprof)
- 符合标准规范:符合通用的O指定编译输出(编译输出与源码分离),DESTDIR指定安装位置(安装目录符合GNUInstallDirs标准),自定义DEPDIR指定依赖根目录
优势对比
与其他构建系统相比,IMake具有独特优势:
| 特性 | IMake | CMake | Autotools | Meson |
|---|---|---|---|---|
| 学习曲线 | 平缓(Makefile语法) | 陡峭(专属语法) | 陡峭(M4宏) | 中等(专属语法) |
| 配置简洁性 | ★★★★★ | ★★★☆☆ | ★★☆☆☆ | ★★★★☆ |
| 调试便利性 | ★★★★★ | ★★★☆☆ | ★★☆☆☆ | ★★★☆☆ |
| 跨平台支持 | Linux专注 | 全平台 | 类Unix | 全平台 |
| 可视化配置 | 内置menuconfig | 需额外工具 | 无 | 有限支持 |
| 生态集成 | 完善 | 丰富 | 成熟 | 成长中 |
适用场景与目标用户
理想应用场景
- 嵌入式Linux开发:交叉编译支持完善,配置简单
- 系统级软件开发:内核模块支持良好,符合Linux标准
- 开源库开发:符合GNU标准,易于其他项目集成
- 大型项目构建:模块化设计,支持组件化开发,menuconfig图形配置更直观
目标用户群体
- 追求构建系统简单性和透明度的开发者
- 主要工作在Linux环境的软件工程师
- 对Autotools/CMake/Meson复杂性感到沮丧的开发者
- 希望深度控制构建过程的资深程序员
编译模板功能特性详解
IMake可脱离CBuild-ng独立使用,建议用户使用模板 inc.makes 并设置 INC_MAKES 启用相应的模板
inc.makes默认只启用 inc.env.mk 和 inc.ins.mkINC_MAKES的值可以是disenvconfappmoddisins的组合- disenv: 不启用 inc.env.mk
- conf : 启用 inc.conf.mk
- app : 启用 inc.app.mk
- mod : 启用 inc.mod.mk
- disins: 不启用 inc.ins.mk
环境模板 inc.env.mk
- 环境模板被应用编译和内核模块编译共用
- Classic Build 时此模板作用是设置编译输出目录
WORKDIR,设置并导出交叉编译环境或本地编译环境 - Yocto Build 时编译输出目录和交叉编译环境由
bitbake设置并导出
环境模板的函数说明
$(call get_version,版本所在的文件,版本宏变量名,分隔符): 提取文件中定义的版本- 文件中定义版本需要遵循如下格式
#define 版本宏变量名 0xabcdef,提取出每两个字符为一组(第1个字符为0会去掉),指定分隔符隔开 - 例如:
json.h中定义了#define JSON_VERSION 0x030305,$(call get_version,json.h,JSON_VERSION, )提取出3 3 5
- 文件中定义版本需要遵循如下格式
$(call link_hdrs): 根据 SEARCH_HDRS 变量的值自动生成查找头文件的 CFLAGS$(call link_libs): 自动生成查找库文件的 LDFLAGS$(call install_lics): 安装 license 文件到/usr/local/license/$(PACKAGE_NAME)$(eval $(call ft-config,CONFIG配置名,CONFIG配置值y时的配置,CONFIG配置值不为y时的配置)): 动态特性配置- 根据
.config中指定配置名的值设置变量FT_CONFIG的值
- 根据
$(eval $(call FT-CONFIG,CONFIG配置名,CONFIG配置值y时的配置,CONFIG配置值不为y时的配置)): 动态特性配置- 作用同上,只是
ft-config函数在NATIVE_BUILD=y时会将CONFIG配置名改为CONFIG配置名_NATIVE;而FT-CONFIG函数不会
- 作用同上,只是
环境模板的变量说明
-
PACKAGE_NAME : 包的名称 (要和 DEPS语句的包名一致,本地编译的 PACKAGE_NAME 不需要加后缀
-native) -
PACKAGE_ID : 只读,包的实际名称,交叉编译时等于 PACKAGE_NAME 的值,本地编译时会加上后缀
-native -
INSTALL_HDR : 头文件安装的子文件夹,默认值等于 PACKAGE_NAME 的值
-
PACKAGE_DEPS : 包的依赖列表,未来可能会删除
-
SEARCH_HDRS : 查找头文件子目录列表,默认值等于 PACKAGE_DEPS 的值
-
ENV_BUILD_TYPE : CC 优化等级选项,默认取值是
optimized,总共4个选项:- debug : OPTIMIZER_FLAG 默认取值
-O0 -g -ggdb - minsize : OPTIMIZER_FLAG 默认取值
-Os - optimized : OPTIMIZER_FLAG 默认取值
-O2 - release : OPTIMIZER_FLAG 默认取值
-O3
- debug : OPTIMIZER_FLAG 默认取值
-
OPTIMIZER_FLAG : 优化等级值
-
WORKDIR : 工作目录
- 交叉编译默认值为
$(ENV_CROSS_ROOT)/objects/$(PACKAGE_NAME),本地编译默认值为$(ENV_NATIVE_ROOT)/objects/$(PACKAGE_NAME) - OBJ_PREFIX / INS_PREFIX / DEP_PREFIX / PATH_PREFIX 的默认定义都在此目录下
- 交叉编译默认值为
-
OBJ_PREFIX : 顶层编译输出目录
-
OBJ_SUBDIR : 编译输出子目录,一般用于有多个独立子模块组成的 Makefile
- 可以使用
make O=xxx改变默认值
- 可以使用
-
INS_PREFIX : 顶层编译安装目录,默认取值
$(WORKDIR)/image- 可以使用
make DESTDIR=xxx改变默认值 - Classic Build 时自动生成的顶层 Makefile 执行安装到全局 sysroot、准备依赖和安装到 rootfs 时,会改变此值
- 可以使用
-
INS_TOPDIR : Classic Build 的首次安装目录,默认取值
$(WORKDIR)/image- Classic Build 时第一次安装到
INS_TOPDIR,再次安装时(安装到全局 sysroot、准备依赖和安装到 rootfs 时)从INS_TOPDIR安装到改变后的INS_PREFIX
- Classic Build 时第一次安装到
-
INS_SUBDIR : Classic Build 使用
CMakeAutotoolsMeson编译时的安装子目录,默认值为/usr,则真正的安装目录为$(INS_TOPDIR)$(INS_SUBDIR) -
DEP_PREFIX : 顶层依赖查找目录
- 可以使用
make DEPDIR=xxx改变默认值
- 可以使用
-
PATH_PREFIX : 顶层本地工具查找目录
-
SYS_PREFIX : Classic Build 全局的顶层编译输出和安装的目录
-
NATIVE_DEPEND : 交叉编译包依赖本地编译包时需要设置为 y,由
gen_build_chain.by自动设置或由 Recipe (cbuild.bbclass) 导出 -
NATIVE_BUILD : 设置为 y 时表示本地编译(native-compilation),由
gen_build_chain.by自动设置或由 Recipe 导出 -
GLOBAL_SYSROOT : 仅用于 Classic Build,设置为 y 时表示使用全局依赖目录,DEP_PREFIX / PATH_PREFIX 会设置为 SYS_PREFIX 的值,由
gen_build_chain.by自动设置 -
PREPARE_SYSROOT : Classic Build 时在 WORKDIR 目录准备 sysroot, 命令是
$(MAKE) $(PREPARE_SYSROOT) -
DIS_PC_EXPORT : 是否禁止导出 pkg-config 的环境变量
-
CC_TOOL : 如果设置为 clang,将使用 clang 编译链编译(初步支持),否则默认使用 gcc 编译
安装模板 inc.ins.mk
- 安装模板被应用编译和内核模块编译共用
- 安装模板的安装目录基本符合 GNUInstallDirs 标准
base_*dir和hdrdir不属于 GNUInstallDirs 的标准- 安装的根目录是
$(INS_PREFIX)
安装模板的目标和变量说明
-
$(eval $(call install_obj,<ID名>)): 生成安装到指定目录的 Makefile 规则- ID名: 目录名去掉
dir - Makefile 规则的目标:
install_<小写id名>s - 要安装的源文件集的变量名:
INSTALL_<大写ID名>S
- ID名: 目录名去掉
-
已定义的 Makefile 规则
- 目录名在
inc.env.mk中定义
目录名 目录定义(目标文件夹) 要安装的源文件集 Makefile 规则的目标 base_bindir/bin$(INSTALL_BASE_BINS)install_base_binsbase_sbindir/sbin$(INSTALL_BASE_SBINS)install_base_sbinsbase_libdir/lib$(INSTALL_BASE_LIBS)install_base_libsbindir/usr/bin$(INSTALL_BINS)install_binssbindir/usr/sbin$(INSTALL_SBINS)install_sbinslibdir/usr/lib$(INSTALL_LIBS)install_libslibexecdir/usr/libexec$(INSTALL_LIBEXECS)install_libexecshdrdir/usr/include/$(INSTALL_HDR)$(INSTALL_HDRS)install_hdrsincludedir/usr/include$(INSTALL_INCLUDES)install_includesdatadir/usr/share$(INSTALL_DATAS)install_datasinfodir$(datadir)/info$(INSTALL_INFOS)install_infoslocaledir$(datadir)/locale$(INSTALL_LOCALES)install_localesmandir$(datadir)/man$(INSTALL_MANS)install_mansdocdir$(datadir)/doc$(INSTALL_DOCS)install_docssysconfdir/etc$(INSTALL_SYSCONFS)install_sysconfsservicedir/srv$(INSTALL_SERVICES)install_servicessharedstatedir/com$(INSTALL_SHAREDSTATES)install_sharedstateslocalstatedir/var$(INSTALL_LOCALSTATES)install_localstatesrunstatedir/run$(INSTALL_RUNSTATES)install_runstates - 目录名在
-
变量默认值定义
- 编译应用时
inc.app.mk,编译生成的可执行文件会加入到BIN_TARGETS变量,INSTALL_BINARIES已默认赋值为$(BIN_TARGETS) - 编译应用时
inc.app.mk,编译生成的库文件会加入到LIB_TARGETS变量,INSTALL_LIBRARIES已默认赋值为$(LIB_TARGETS)
INSTALL_BASE_BINARIES ?= $(INSTALL_BINARIES) INSTALL_BASE_BINS ?= $(INSTALL_BASE_BINARIES) INSTALL_BINS ?= $(INSTALL_BINARIES) INSTALL_BASE_LIBRARIES ?= $(INSTALL_LIBRARIES) INSTALL_BASE_LIBS ?= $(INSTALL_BASE_LIBRARIES) INSTALL_LIBS ?= $(INSTALL_LIBRARIES) INSTALL_HDRS ?= $(INSTALL_HEADERS) - 编译应用时
-
$(eval $(call install_ext,<ID名>)): 生成安装到指定目录的指定子目录的 Makefile 模式规则- ID名: 目录名去掉
dir - Makefile 模式规则的目标:
install_<小写id名>s_%,%匹配小写字符 - 要安装的源文件集和目标子文件夹的变量名:
INSTALL_<大写ID名>S_xxx,xxx和目标中的模式匹配部分的字符串相同- 定义的值前面部分是要安装的源文件集,最后一项是以斜杆
/开头的指定子目录
- 定义的值前面部分是要安装的源文件集,最后一项是以斜杆
- ID名: 目录名去掉
-
已定义的 Makefile 模式规则
- 模式规则
install_todir_xxx和install_tofile_xxx不是由install_ext定义的install_todir_xxx: 安装到根目录的指定子目录的 Makefile 模式规则install_tofile_xxx: 安装到根目录的指定文件的 Makefile 模式规则,用于安装文件并重命名
目录名 目标文件夹 设置安装的变量名 Makefile 规则的模式目标 includedir/usr/include<指定子目录>$(INSTALL_INCLUDES_<xxx>)install_includes_%datadir/usr/share<指定子目录>$(INSTALL_DATAS_<xxx>)install_datas_%sysconfdir/etc<指定子目录>$(INSTALL_SYSCONFS_<xxx>)install_sysconfs_%<指定子目录>$(INSTALL_TODIR_<xxx>)install_todir_%<指定子文件>$(INSTALL_TOFILE_<xxx>)install_tofile_% - 模式规则
-
模式规则例子
-
创建2个空白文件 testa 和 testb,Makefile 内容如下:
INSTALL_DATAS_test = testa testb /testa/testb INSTALL_TODIR_test = testa testb /usr/local/bin INSTALL_TOFILE_testa = testa /etc/a.conf INSTALL_TOFILE_testb = testa /etc/b.conf all: install_datas_test install_todir_test install_tofile_testa install_tofile_testb include $(ENV_MAKE_DIR)/inc.ins.mk -
运行 make 安装后的文件树
image ├── etc │ ├── a.conf │ └── b.conf └── usr ├── local │ └── bin │ ├── testa │ └── testb └── share └── testa └── testb ├── testa └── testb
-
应用模板 inc.app.mk
- 应用模板用于编译动态库、静态库和可执行文件
应用模板的目标说明
- LIBA_NAME: 编译单个静态库时需要设置静态库名
- 编译生成的静态库文件路径会加入到
LIB_TARGETS变量
- 编译生成的静态库文件路径会加入到
- LIBSO_NAME: 编译单个动态库时需要设置动态库名
- LIBSO_NAME 可以设置为
库名 主版本号 次版本号 补丁版本号格式,例如LIBSO_NAME = libtest.so 1 2 3编译生成动态库 libtest.so.1.2.3,并创建符号链接 libtest.so 和 libtest.so.1LIBSO_NAME = libtest.so 1 2编译生成动态库 libtest.so.1.2 ,并创建符号链接 libtest.so 和 libtest.so.1LIBSO_NAME = libtest.so 1编译生成动态库 libtest.so.1 ,并创建符号链接 libtest.soLIBSO_NAME = libtest.so编译生成动态库 libtest.so
- VERSION_FILE 和 VERSION_NAME:如果定义了这两个变量,LIBSO_NAME不用设置版本号,模板内部会自动调用
get_version提取文件中版本号- 即实际执行了
$(eval $(call add-libso-build,$(LIBSO_NAME) $(call get_version,$(VERSION_FILE),$(VERSION_NAME), ),$(SRCS)))
- 即实际执行了
- 如果 LIBSO_NAME 带版本号,默认指定的 soname 是
libxxxx.so.x,可以通过 LDFLAGS 覆盖默认值- 例如
LDFLAGS += -Wl,-soname=libxxxx.so
- 例如
- 编译生成的动态库文件路径和符号链接路径会加入到
LIB_TARGETS变量
- LIBSO_NAME 可以设置为
- BIN_NAME: 编译单个可执行文件时需要设置可执行文件名
- 编译生成的可执行文件会加入到
BIN_TARGETS变量
- 编译生成的可执行文件会加入到
应用模板的函数说明
-
compile_obj: 创建一组编译c/cxx/asm源文件规则
$(eval $(call compile_obj,源文件后缀,编译器))- 一般不会被用户调用,除非有新的C++源文件后缀
-
compile_vobj: 创建一条自定义编译源文件规则
$(eval $(call compile_vobj,源文件后缀,编译器,虚拟源文件,真实源文件))(虚拟源文件需要加入到变量VSRCS)- 一般用于一个源文件根据不同的CFLAGS编译出不同的.o文件,例子见 JCore 的Makefile
-
compile_oobj: 创建一组自定义编译输出目录的源文件规则
$(eval $(call compile_oobj,源文件后缀,编译器,虚拟源文件列表))(虚拟源文件需要加入到变量VSRCS)- 一般用于生成在输出目录的源文件的编译,例如kconfig的编译
-
add-liba-build: 创建一条编译静态库规则
$(eval $(call add-liba-build,静态库名,源文件列表))$(eval $(call add-liba-build,静态库名,源文件列表,依赖的静态库路径列表))- 打包新静态库时会将依赖的静态库追加到这个生成的新静态库
$(eval $(call add-liba-build,静态库名,源文件列表,依赖的静态库路径列表,私有的CFLAGS参数))$(eval $(call add-liba-build,静态库名,源文件列表,依赖的静态库路径列表,私有的CFLAGS参数,额外的依赖列表))
-
add-libso-build: 创建一条编译动态库规则
$(eval $(call add-libso-build,动态库名,源文件列表))- 动态库名可以设置为
库名 主版本号 次版本号 补丁版本号格式,参考 LIBSO_NAME 的说明
- 动态库名可以设置为
$(eval $(call add-libso-build,动态库名,源文件列表,链接参数))- 注意函数中有逗号要用变量覆盖,例如
$(eval $(call add-libso-build,动态库名,源文件列表,-Wl$(comma)-soname=libxxxx.so))
- 注意函数中有逗号要用变量覆盖,例如
$(eval $(call add-libso-build,动态库名,源文件列表,链接参数,私有的CFLAGS参数))$(eval $(call add-libso-build,动态库名,源文件列表,链接参数,私有的CFLAGS参数,额外的依赖列表))
-
add-libso-build: 创建一条编译可执行文件规则
$(eval $(call add-bin-build,可执行文件名,源文件列表))$(eval $(call add-bin-build,可执行文件名,源文件列表,链接参数))$(eval $(call add-bin-build,可执行文件名,源文件列表,链接参数,私有的CFLAGS参数))$(eval $(call add-bin-build,可执行文件名,源文件列表,链接参数,私有的CFLAGS参数,额外的依赖列表))
-
set_flags: 单独为指定源码集合设置编译标记
$(call set_flags,标记名称,源文件列表,标记值)- 编译标志可以是C/C++编译标记(CFLAGS)或汇编标记(AFLAGS)
- 例如
$(call set_flags,CFLAGS,main.c src/read.c src/write.c,-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE)
-
set_links: 设置链接库,加上
-l,此函数用于一个库同时提供静态库和动态库时,强制链接它的静态库$(call set_links,静态库列表)$(call set_links,静态库列表,动态库列表)
注: 提供上述函数的原因是可以在一个 Makefile 中编译出多个库或可执行文件
应用模板的可设置变量说明
-
SRC_PATH: 包中源码所在的目录,默认值(
.)是包的根目录,也有的包将源码放在 src 下- 也可以指定包下多个(不交叉)目录的源码,例如
SRC_PATH = src1 src2 src3
- 也可以指定包下多个(不交叉)目录的源码,例如
-
IGNORE_PATH: 查找源码文件时,忽略搜索的目录名集合,默认已忽略
.git scripts output文件夹 -
REG_SUFFIX: 支持查找的源码文件的后缀名,默认查找以
c cpp S为后缀的源码文件- 可以修改为其它类型的文件,从 c 和 CXX_SUFFIX / ASM_SUFFIX 定义的类型中选择
- CXX_SUFFIX: C++类型的文件后缀名,默认定义为
cc cp cxx cpp CPP c++ C - ASM_SUFFIX: 汇编类型的文件后缀名,默认定义为
S s asm
- CXX_SUFFIX: C++类型的文件后缀名,默认定义为
- 如果支持非 CXX_SUFFIX / ASM_SUFFIX 默认定义类型的文件,只需要修改 REG_SUFFIX 和 CXX_SUFFIX / ASM_SUFFIX ,并定义函数
- 例如增加 cxx 类型的支持(CXX_SUFFIX 已有定义 cxx):
REG_SUFFIX = c cpp S cxx include $(ENV_MAKE_DIR)/inc.app.mk - 例如增加 CXX 类型的支持(CXX_SUFFIX 还未定义 CXX):
REG_SUFFIX = c cpp S CXX CXX_SUFFIX = cc cp cxx cpp CPP c++ C CXX include $(ENV_MAKE_DIR)/inc.app.mk $(eval $(call compile_obj,CXX,$$(CXX)))
- 可以修改为其它类型的文件,从 c 和 CXX_SUFFIX / ASM_SUFFIX 定义的类型中选择
-
USING_CXX_BUILD_C: 设置为 y 时
*.c文件也用 CXX 编译,$(CCC)指定了具体的C编译器 -
SRCS: 所有的源码文件,默认是 SRC_PATH 下的所有的
*.c *.cpp *.S文件- 如果用户指定了 SRCS,也可以设置 SRC_PATH,将 SRC_PATH 和 SRC_PATH 下的 include 加入到头文件搜索的目录
- 如果用户指定了 SRCS,忽略 IGNORE_PATH 的值
-
VSRCS: 虚拟的源码文件,和函数
compile_vobj或compile_oobj共用,用于编译输出目录的.o编译文件对应的源码目录虚拟源码文件 -
ASRCS: 附加的源码文件,可能是由脚本生成在输出目录,直接设置此变量,不用使用
compile_oobj,方法更简单
-
CPFLAGS: 用户可以设置C和C++共有的一些全局编译标记
-
CFLAGS: 用户可以设置C的一些全局编译标记
-
CXXFLAGS: 用户可以设置C++的一些全局编译标记
-
AFLAGS: 用户可以设置一些全局汇编标记
-
LDFLAGS: 用户可以设置一些全局链接标记
-
IMAKE_CPFLAGS: 用户可以从make或环境传入C和C++共有的一些全局编译标记
-
IMAKE_LDFLAGS: 用户可以从make或环境传入一些全局链接标记
配置模板 inc.conf.mk
- 配置模板提供 Kongfig 配置参数
配置模板的目标说明
- loadconfig: 如果 .config 不存在,加载 DEF_CONFIG 指定的默认配置
- defconfig: 还原当前配置为 DEF_CONFIG 指定的默认配置
- menuconfig: 图形化配置工具
- cleanconfig: 清理配置文件
- xxx_config: 将 CONF_SAVE_PATH 下的 xxx_config 作为当前配置
- xxx_saveconfig: 将当前配置保存到 CONF_SAVE_PATH 下的 xxx_config
- xxx_defonfig: 将 CONF_SAVE_PATH 下的 xxx_defconfig 作为当前配置
- xxx_savedefconfig: 将当前配置保存到 CONF_SAVE_PATH 下的 xxx_defconfig
配置模板的可设置变量说明
- CONF_WORKDIR: 编译输出目录,保持默认即可
- CONF_SRC: kconfig 工具的源码目录,目前是在
$(ENV_TOP_DIR)/scripts/kconfig,和实际一致即可 - CONF_PATH: kconfig 工具的安装目录,和实际一致即可
- CONF_PREFIX: 设置 conf 运行的变量,主要是下面两个设置
srctree=path_name: Kconfig 文件中 source 其它配置参数文件的相对的目录是 srctree 指定的目录,如果不指定,默认是运行conf/mconf命令的目录CONFIG_="": 设置生成的 .config 和 config.h 文件中的选项名称(对比 Kconfig 对应的选项名称)的前缀,不设置时,默认值是CONFIG_,本例的设置是无前缀
- CONF_HEADER: 设置生成的 config.h 中使用的包含宏,默认值是
__大写包名_CONFIG_H__- kconfig 生成的头文件默认不包含宏
#ifndef xxx ... #define xxx ... #endif,本模板使用 sed 命令添加了宏
- kconfig 生成的头文件默认不包含宏
- KCONFIG: 配置参数文件,默认是包下的 Kconfig 文件
- CONF_SAVE_PATH: 配置文件的获取和保存目录,默认是包下的 config 目录
- CONF_APPEND_CMD: config 改变时追加运行的命令
注: 目录下的 Kconfig 文件也说明了如何写配置参数
scripts/kconfig 工程说明
- 源码完全来自 linux-6.12.28 内核的
scripts/kconfig - 在原始代码的基础上增加了命令传入参数
CONFIG_PATHAUTOCONFIG_PATHAUTOHEADER_PATHRUSTCCFG_PATH,原先这些参数要作为环境变量传入 - Makefile 是完全重新编写的
驱动模板 inc.mod.mk
- 驱动模板用于编译外部内核模块
驱动模板的 Makefile 部分说明 (KERNELRELEASE 为空时)
-
支持的目标
- modules: 编译驱动
- modules_clean: 清理内核模块的编译输出
- modules_install: 安装内核模块
- 驱动默认的安装路径为
$(INS_PREFIX)/lib/modules/<kernel_release>/extra/
- 驱动默认的安装路径为
- symvers_install: 安装 Module.symvers 符号文件到指定位置(已设置此目标为
install_hdrs目标的依赖)
-
可设置的变量
- MOD_PATH: 模块Kbuild的文件路径,默认值是当前目录
- MOD_MAKES: 用户指定一些模块自己的信息,例如 XXXX=xxxx
- KERNEL_SRC: Linux 内核源码目录 (必须)
- KERNEL_OUT: Linux 内核编译输出目录 (
make -O $(KERNEL_OUT)编译内核的情况下必须)
驱动模板的 Kbuild 部分说明 (KERNELRELEASE 有值时)
-
支持的目标
- MOD_NAME: 模块名称,可以是多个模块名称使用空格隔开
- MOD_NAME: 模块名称,可以是多个模块名称使用空格隔开
-
可设置的变量
- IGNORE_PATH: 查找源码文件时,忽略搜索的目录名集合,默认已忽略
.git scripts output文件夹 - SRCS: 所有的 C 和汇编源码文件,默认是当前目录下的所有的
*.c *.S文件 ccflags-yasflags-yldflags-y: 分别对应内核模块编译、汇编、链接时的参数- IMAKE_CCFLAGS: 用户可以从make或环境传入内核模块编译的一些全局编译标记
- IGNORE_PATH: 查找源码文件时,忽略搜索的目录名集合,默认已忽略
-
提供的函数
$(call translate_obj,源码文件集): 将源码文件集名字转换为 KBUILD 需要的*.o格式,不管源码是不是以$(src)/开头$(call set_flags,标记名称,源文件列表,标记值): 单独为指定源码集合设置编译标记(CFLAGS)或汇编标记(AFLAGS)
-
其它说明
-
如果 MOD_NAME 含有多个模块名称,需要用户自己填写各个模块下的对象,例如
MOD_NAME = mod1 mod2 mod1-y = a1.o b1.o c1.o mod2-y = a2.o b2.o c2.o -
使用源码和编译输出分离时, 需要先将 Kbuild 或 Makefile 复制到
OBJ_PREFIX目录下,如果不想复制,需要修改内核源码的scripts/Makefile.modpost,linux-5.19 内核和最新版本的 LTS 内核已合并此补丁-include $(if $(wildcard $(KBUILD_EXTMOD)/Kbuild), \ - $(KBUILD_EXTMOD)/Kbuild, $(KBUILD_EXTMOD)/Makefile) +include $(if $(wildcard $(src)/Kbuild), $(src)/Kbuild, $(src)/Makefile)
-
使用示例
实际工程使用
纯头文件应用工程
- 纯头文件工程只有安装
- 头文件是安装在include的PACKAGE_NAME子目录下,防止大工程头文件重名
PACKAGE_NAME = xxx
INSTALL_HEADERS:= $(wildcard src/*.h)
.PHONY: all clean install
all:
@echo "Build $(PACKAGE_NAME) Done!"
include inc.makes
clean:
@echo "Clean $(PACKAGE_NAME) Done."
install: install_hdrs
@echo "Install $(PACKAGE_NAME) to $(INS_PREFIX) Done."
单库应用工程
- 指定生成的静态库LIBA_NAME和动态库LIBSO_NAME
- 从指定文件VERSION_FILE的指定版本宏VERSION_NAME自动提取版本,生成的动态库为libxxx.so.x.y.z
PACKAGE_NAME = xxx
VERSION_FILE := xxx.hpp
VERSION_NAME := XXX_VERSION
LIBA_NAME := libxxx.a
LIBSO_NAME := libxxx.so
CPFLAGS += -I./src
INSTALL_HEADERS:= $(wildcard src/*.hpp)
.PHONY: all clean install
all:
@echo "Build $(PACKAGE_NAME) Done!"
INC_MAKES := app
include inc.makes
all: $(LIB_TARGETS)
clean: clean_objs
@rm -f $(LIB_TARGETS)
@echo "Clean $(PACKAGE_NAME) Done."
install: install_hdrs install_libs
@echo "Install $(PACKAGE_NAME) to $(INS_PREFIX) Done."
多库和可执行文件混合应用工程
- SEARCH_HDRS 指定头文件搜索的子路径,这样源代码可以写成
#include "aaa.h"而不是#include "mmm/aaa.h" - object_byte_size/frame_byte_size 是指定最大对象和函数帧大小,大于该值时有编译警告,用于调试过大的局部变量,不是必需
- 可选ENV_BUILD_TYPE指定优化等级,release是
-O3,不指定时是-O2 - set_flags是设置单个文件的编译标志,参考内核编译指定单文件的编译编译标志设置,而CPFLAGS是全局的
PACKAGE_NAME = xxx
SEARCH_HDRS := mmm nnn
INSTALL_HEADERS:= $(wildcard src/*.hpp)
CPFLAGS += -Isrc -Wno-unused-parameter
.PHONY: all clean install
all:
@echo "Build $(PACKAGE_NAME) Done!"
INC_MAKES := app
object_byte_size= 65536
frame_byte_size = 16384
ENV_BUILD_TYPE := release
include inc.makes
staticlib := libxxx.a
sharedlib := libxxx.so $(call get_version,src/xxx.hpp,JXXX_VERSION, )
libsrcs := $(wildcard src/*.cpp)
$(call set_flags,CFLAGS,src/xxx_message.cpp,-Wno-missing-field-initializers)
LLIBS := $(addprefix -l,jcore ljson g711 g722 bcg729 opus fdk-aac)
$(eval $(call add-liba-build,$(staticlib),$(libsrcs)))
$(eval $(call add-libso-build,$(sharedlib),$(libsrcs),$(LLIBS)))
server_srcs := test/xxx_server.cpp
server_libs := $(addprefix -l,jcore ljson $(PACKAGE_NAME))
$(eval $(call add-bin-build,xxx_server,$(server_srcs),$(server_libs),,$(OBJ_PREFIX)/lib$(PACKAGE_NAME).so))
client_srcs := test/xxx_client.cpp
client_libs := $(LLIBS) -l$(PACKAGE_NAME)
$(eval $(call add-bin-build,xxx_client,$(client_srcs),$(client_libs),,$(OBJ_PREFIX)/lib$(PACKAGE_NAME).so))
all: $(BIN_TARGETS) $(LIB_TARGETS)
clean: clean_objs
@rm -f $(LIB_TARGETS) $(BIN_TARGETS)
@echo "Clean $(PACKAGE_NAME) Done."
install: install_hdrs install_libs install_bins
@echo "Install $(PACKAGE_NAME) to $(INS_PREFIX) Done."
单驱动工程
ifneq ($(KERNELRELEASE),)
MOD_NAME = xxx
else
PACKAGE_NAME = xxx
SEARCH_HDRS = mmm nnn
all: modules
clean: modules_clean
install: modules_install
endif
INC_MAKES := mod
include $(src)/inc.makes
单驱动含测试程序工程
- 通过KERNELRELEASE宏区分是Kbuild编译部分和应用编译部分
ifneq ($(KERNELRELEASE),)
MOD_NAME := xxx
SRCS := $(wildcard $(src)/src/*.c)
INC_MAKES := mod
include $(src)/inc.makes
ccflags-y += -I$(src)/src
else
PACKAGE_NAME = xxx
CPFLAGS += -Isrc
INSTALL_HEADERS:= src/xxx.h
all: modules
clean: modules_clean
install: modules_install
INC_MAKES := app mod
include inc.makes
$(eval $(call add-bin-build,xxx_test,test/xxx_test.c))
all: $(BIN_TARGETS)
clean: clean_objs
@rm -f $(BIN_TARGETS)
@echo "Clean $(PACKAGE_NAME) Done."
install: install_bins install_hdrs
@echo "Install $(PACKAGE_NAME) to $(INS_PREFIX) Done."
endif
结语:回归本质,简化构建
IMake的出现是对当前复杂构建系统生态的一次重要反思。它证明了一个观点:强大的功能不一定需要复杂的配置,简单的设计也可以应对复杂的构建需求。
探索IMake,体验构建系统的简单之美! 如果您正在寻找一个既强大又简单的构建解决方案,IMake值得您的尝试。

29

被折叠的 条评论
为什么被折叠?



