IMake:让构建回归Makefile本质,前所未有的简单体验

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具有独特优势:

特性IMakeCMakeAutotoolsMeson
学习曲线平缓(Makefile语法)陡峭(专属语法)陡峭(M4宏)中等(专属语法)
配置简洁性★★★★★★★★☆☆★★☆☆☆★★★★☆
调试便利性★★★★★★★★☆☆★★☆☆☆★★★☆☆
跨平台支持Linux专注全平台类Unix全平台
可视化配置内置menuconfig需额外工具有限支持
生态集成完善丰富成熟成长中

适用场景与目标用户

理想应用场景

  1. 嵌入式Linux开发:交叉编译支持完善,配置简单
  2. 系统级软件开发:内核模块支持良好,符合Linux标准
  3. 开源库开发:符合GNU标准,易于其他项目集成
  4. 大型项目构建:模块化设计,支持组件化开发,menuconfig图形配置更直观

目标用户群体

  • 追求构建系统简单性和透明度的开发者
  • 主要工作在Linux环境的软件工程师
  • 对Autotools/CMake/Meson复杂性感到沮丧的开发者
  • 希望深度控制构建过程的资深程序员

编译模板功能特性详解

IMake可脱离CBuild-ng独立使用,建议用户使用模板 inc.makes 并设置 INC_MAKES 启用相应的模板

  • inc.makes 默认只启用 inc.env.mk 和 inc.ins.mk
  • INC_MAKES 的值可以是 disenv conf app mod disins 的组合
    • 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
  • 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
  • INS_SUBDIR : Classic Build 使用 CMake Autotools Meson 编译时的安装子目录,默认值为 /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_*dirhdrdir 不属于 GNUInstallDirs 的标准
    • 安装的根目录是 $(INS_PREFIX)
安装模板的目标和变量说明
  • $(eval $(call install_obj,<ID名>)): 生成安装到指定目录的 Makefile 规则

    • ID名: 目录名去掉 dir
    • Makefile 规则的目标: install_<小写id名>s
    • 要安装的源文件集的变量名: INSTALL_<大写ID名>S

  • 已定义的 Makefile 规则

    • 目录名在 inc.env.mk 中定义
    目录名目录定义(目标文件夹)要安装的源文件集Makefile 规则的目标
    base_bindir/bin$(INSTALL_BASE_BINS)install_base_bins
    base_sbindir/sbin$(INSTALL_BASE_SBINS)install_base_sbins
    base_libdir/lib$(INSTALL_BASE_LIBS)install_base_libs
    bindir/usr/bin$(INSTALL_BINS)install_bins
    sbindir/usr/sbin$(INSTALL_SBINS)install_sbins
    libdir/usr/lib$(INSTALL_LIBS)install_libs
    libexecdir/usr/libexec$(INSTALL_LIBEXECS)install_libexecs
    hdrdir/usr/include/$(INSTALL_HDR)$(INSTALL_HDRS)install_hdrs
    includedir/usr/include$(INSTALL_INCLUDES)install_includes
    datadir/usr/share$(INSTALL_DATAS)install_datas
    infodir$(datadir)/info$(INSTALL_INFOS)install_infos
    localedir$(datadir)/locale$(INSTALL_LOCALES)install_locales
    mandir$(datadir)/man$(INSTALL_MANS)install_mans
    docdir$(datadir)/doc$(INSTALL_DOCS)install_docs
    sysconfdir/etc$(INSTALL_SYSCONFS)install_sysconfs
    servicedir/srv$(INSTALL_SERVICES)install_services
    sharedstatedir/com$(INSTALL_SHAREDSTATES)install_sharedstates
    localstatedir/var$(INSTALL_LOCALSTATES)install_localstates
    runstatedir/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_xxxxxx 和目标中的模式匹配部分的字符串相同
      • 定义的值前面部分是要安装的源文件集,最后一项是以斜杆 / 开头的指定子目录

  • 已定义的 Makefile 模式规则

    • 模式规则 install_todir_xxxinstall_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.1
      • LIBSO_NAME = libtest.so 1 2 编译生成动态库 libtest.so.1.2 ,并创建符号链接 libtest.so 和 libtest.so.1
      • LIBSO_NAME = libtest.so 1 编译生成动态库 libtest.so.1 ,并创建符号链接 libtest.so
      • LIBSO_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 变量
  • 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 / 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)))
      
  • 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_vobjcompile_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 文件
  • CONF_SAVE_PATH: 配置文件的获取和保存目录,默认是包下的 config 目录
  • CONF_APPEND_CMD: config 改变时追加运行的命令

注: 目录下的 Kconfig 文件也说明了如何写配置参数

scripts/kconfig 工程说明
  • 源码完全来自 linux-6.12.28 内核的 scripts/kconfig
  • 在原始代码的基础上增加了命令传入参数 CONFIG_PATH AUTOCONFIG_PATH AUTOHEADER_PATH RUSTCCFG_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: 模块名称,可以是多个模块名称使用空格隔开

  • 可设置的变量

    • IGNORE_PATH: 查找源码文件时,忽略搜索的目录名集合,默认已忽略 .git scripts output 文件夹
    • SRCS: 所有的 C 和汇编源码文件,默认是当前目录下的所有的 *.c *.S 文件
    • ccflags-y asflags-y ldflags-y: 分别对应内核模块编译、汇编、链接时的参数
    • IMAKE_CCFLAGS: 用户可以从make或环境传入内核模块编译的一些全局编译标记

  • 提供的函数

    • $(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值得您的尝试。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值