欢迎光临
我们一直在努力

Party TOML 配置体系:通过 `NIMMAKE.toml` 实现组件级声明式构建

Party TOML 配置体系:通过 NIMMAKE.toml 实现组件级声明式构建

Nimmake 允许在每个 Party(组件)目录下放置 NIMMAKE.toml 文件,由 Party 自己声明源文件列表、头文件目录和宏定义,实现「组件自治」的构建模式。


目录

  • 概述
  • Party TOML 工作机制
  • NIMMAKE.toml 配置格式
  • 两种工作模式
    • 4.1 自动发现模式(无 TOML)
    • 4.2 声明式模式(有 TOML)
    • 4.3 模式对比
  • 完整实战示例
  • 与 CMake 的对比
  • 最佳实践

  • 1. 概述

    核心思想

    传统的构建系统(如 CMake、Makefile)在中心脚本中声明所有源文件:

    CMakeLists.txt:
    add_executable(test main.c driver.c hal.c …)

    当项目膨胀到数十个组件、上千个源文件时,中心脚本变得难以维护。

    Nimmake 的 Party TOML 方案将「源文件清单」下放给每个组件自己管理:

    Core/NIMMAKE.toml ← Core 组件自己说「我有这些源文件」
    Drivers/NIMMAKE.toml ← Drivers 组件自己说「我有这些源文件」
    App/NIMMAKE.toml ← App 组件自己说「我有这些源文件」

    Nimmake.py ← 主脚本只关心「依赖关系」和「构建目标」

    关键代码:GenericParty.setup()

    位于 [ThirdParty.py](file:///d:/MyCodeNew/nimmake/src/nimmake/thirdParties/ThirdParty.py#L521-L597):

    class GenericParty(ThirdParty):
    def setup(self) > None:
    pymake_toml = self.dir / TOML_PYMAKEX # "NIMMAKE.toml"
    if pymake_toml.exists():
    with open(pymake_toml, "rb") as f:
    data = tomllib.load(f)

    tgt = data.get("target", [])
    if tgt and isinstance(tgt, dict):
    srcs = tgt.get("srcs", [])
    incs = tgt.get("incs", [])
    defines = tgt.get("defines", [])
    include_macros = tgt.get("include_macros", [])

    # 如果 build_type 不是 HEADER, 添加源文件
    if self.build_type != BuildType.HEADER.name:
    for src in srcs:
    self.srcs.append(self.dir / src)

    for inc in incs:
    self.incs.append(self.dir / inc)

    # 解析 defines:["DEBUG=1"] → {"DEBUG": "1"}
    if defines and isinstance(defines, list):
    for df in defines:
    if not df: continue
    df_lst = df.split("=")
    if len(df_lst) != 2: continue
    self.defines.update({df_lst[0]: df_lst[1]})

    # 解析 include_macros
    if include_macros and isinstance(include_macros, list):
    for macro in include_macros:
    if not macro: continue
    macro_lst = macro.split("=")
    if len(macro_lst) != 2: continue
    self.include_macros.update({macro_lst[0]: macro_lst[1]})
    else:
    # 无 TOML → 自动发现文件
    self.incs = self.include_dirs(...)
    if self.build_type != BuildType.HEADER.name:
    self.srcs = self.sources(...)


    2. Party TOML 工作机制

    Nimmake.py

    hlp.Parties("CORE", "Core")


    GenericParty("CORE", root="Core")


    GenericParty.setup()

    ┌─────────┴──────────┐
    ▼ ▼
    Core/NIMMAKE.toml 没有 TOML 文件
    存在? │
    │ ▼
    ▼ 自动发现(目录扫描)
    读取 TOML 配置 │
    │ ▼
    ▼ srcs = 扫描所有
    srcs = 明确的列表 .c/.h/.s 文件
    incs = 明确的目录
    defines = 明确的宏 incs = 自动收集
    │ include 目录

    ┌─────────────────┐
    │ Party 构建数据 │
    │ srcs, incs, │
    │ defines, deps │
    └────────┬────────┘

    ParseParties 处理依赖

    编译 → 归档 → 链接

    关键设计

    环节说明
    文件命名 NIMMAKE.toml(常量 TOML_PYMAKEX 定义在 [configs.py](file:///d:/MyCodeNew/nimmake/src/nimmake/configs.py#L53))
    旧名兼容 PYMAKEX.toml 同样有效(示例中两者共存)
    发现时机 GenericParty.__init__() → setup() 中自动检测
    优先级 TOML 配置完全替代自动发现(有 TOML 就不扫描目录)
    解析引擎 Python 3.11+ 内置 tomllib,无外部依赖

    3. Nimmake.toml 配置格式

    基础结构

    [target]
    srcs = [
    "Src/main.c",
    "Src/stm32f4xx_hal_msp.c",
    "Src/stm32f4xx_it.c",
    "Src/system_stm32f4xx.c",
    "Src/syscalls.c",
    "Src/sysmem.c",
    ]
    incs = ["Inc"]

    defines = ["DEBUG=1", "USE_HAL_DRIVER=1"]
    include_macros = ["BOARD_NAME=\\"STM32F407\\""]

    字段说明

    字段类型必填说明示例
    srcs string[] 源文件列表(相对于 TOML 所在目录) ["Src/main.c", "Src/hal.c"]
    incs string[] 头文件搜索目录 ["Inc", "Inc/config"]
    defines string[] C 宏定义,= 分割键值 ["DEBUG=1", "USE_HAL_DRIVER=1"]
    include_macros string[] 包含宏(同 defines,语义更明确) ["BOARD_ID=345"]

    路径规则

    • 所有路径相对于 TOML 文件所在目录
    • 路径分隔符使用 /(跨平台兼容)
    • 最终路径会被解析为 self.dir / "Src/main.c"

    实际文件示例

    samples/09_arm_llvm/Core/NIMMAKE.toml 文件内容:

    [target]
    srcs = [
    "Src/main.c",
    "Src/stm32f4xx_hal_msp.c",
    "Src/stm32f4xx_it.c",
    "Src/system_stm32f4xx.c",
    "Src/syscalls.c",
    "Src/sysmem.c",
    ]
    incs = ["Inc"]

    可选的注释行

    # defines = ["DEBUG=1", "USE_HAL_DRIVER=1", "BOARD_ID=345", "xxxx"]
    # include_macros = ["DEBUG=1", "USE_HAL_DRIVER=1", "BOARD_ID=345", "xxxx"]

    TOML 支持 # 注释,可以保留常用配置模板以备启用。


    4. 两种工作模式

    4.1 自动发现模式(无 TOML)

    当 Party 目录没有 NIMMAKE.toml 时:

    core = hlp.Parties("CORE", "Core")
    # → GenericParty.setup()
    # → 无 TOML 文件
    # → 自动扫描 Core/ 下所有 .c/.h/.s 文件
    # → 自动收集 include 目录
    # → 使用 hlp.Parties() 传入的 defines

    优点:

    • 零配置,新建文件自动纳入构建
    • 适合快速原型和文件较少的组件

    缺点:

    • 目录扫描耗时(大型 Library 如 HAL 有几百个文件)
    • 无法控制文件加入顺序
    • 可能误扫不需要的文件

    4.2 声明式模式(有 TOML)

    当 Party 目录存在 NIMMAKE.toml 时:

    core = hlp.Parties("CORE", "Core")
    # → GenericParty.setup()
    # → 找到 Core/NIMMAKE.toml
    # → 读取 [target].srcs → 精确的源文件列表
    # → 读取 [target].incs → 精确的头文件目录
    # → 读取 [target].defines → Party 专属宏定义
    # → 完全跳过目录扫描

    优点:

    • 构建速度更快:跳过目录扫描,大型项目节省数秒
    • 精确控制:只编译需要的文件,排除测试、示例文件
    • 可复现性:源文件列表是版本化的,不依赖目录状态
    • IDE 友好:TOML 语法高亮,非开发者也可编辑

    缺点:

    • 新增文件需要手动更新 TOML
    • 少量样板文件维护成本

    4.3 模式对比

    维度自动发现模式声明式模式(TOML)
    配置位置 Nimmake.py(中心化) NIMMAKE.toml(分散化)
    新增文件 自动纳入 需手动添加
    删除文件 自动排除 需手动删除
    构建速度 慢(扫描磁盘 IO) 快(零扫描)
    可复现性 低(依赖目录状态) 高(列表版本化)
    维护成本 初期低,后期高 初期低,后期低
    大型项目 不推荐 推荐
    CI 确定性

    5. 完整实战示例

    项目结构

    my_project/
    ├── Nimmake.py # 主构建脚本
    ├── NIMMAKE_CFG.toml # 编译配置(工具链/芯片/优化)
    ├── Core/
    │ ├── NIMMAKE.toml # Core 组件的构建参数
    │ ├── Inc/
    │ │ └── main.h
    │ └── Src/
    │ ├── main.c
    │ ├── stm32f4xx_hal_msp.c
    │ └── system_stm32f4xx.c
    ├── Drivers/
    │ ├── NIMMAKE.toml # Drivers 组件的构建参数
    │ ├── Inc/
    │ │ └── stm32f4xx_hal.h
    │ └── Src/
    │ ├── stm32f4xx_hal_adc.c
    │ ├── stm32f4xx_hal_dma.c
    │ ├── stm32f4xx_hal_gpio.c
    │ ├── stm32f4xx_hal_rcc.c
    │ └── stm32f4xx_hal_uart.c
    └── startup_stm32f407xx.s

    Core/NIMMAKE.toml

    [target]
    srcs = [
    "Src/main.c",
    "Src/stm32f4xx_hal_msp.c",
    "Src/system_stm32f4xx.c",
    ]
    incs = ["Inc"]

    defines = ["USE_HAL_DRIVER=1"]

    Drivers/NIMMAKE.toml

    [target]
    srcs = [
    "Src/stm32f4xx_hal_adc.c",
    "Src/stm32f4xx_hal_dma.c",
    "Src/stm32f4xx_hal_gpio.c",
    "Src/stm32f4xx_hal_rcc.c",
    "Src/stm32f4xx_hal_uart.c",
    ]
    incs = ["Inc"]

    defines = ["STM32F407xx="]

    Nimmake.py

    from nimmake.Helper import Helper
    from nimmake.configs import BuildType

    toolpath = r"D:\\LLVM\\arm-none-eabi-gcc14\\bin"
    hlp = Helper()

    # ── 全局编译配置 ──
    hlp.TOML() # 读取 NIMMAKE_CFG.toml
    hlp.Update({"TOOLPATH": toolpath, "TOOL": "gcc", "TOOL_PREFIX": "arm-none-eabi-"})
    hlp.Refresh()

    # ── 组件定义 ──
    # Core 组件:从 Core/NIMMAKE.toml 自动获取 srcs/incs/defines
    core = hlp.Parties("CORE", "Core")

    # Drivers 组件:从 Drivers/NIMMAKE.toml 自动获取,编译为静态库
    driver = hlp.Parties(
    "Driver", "Drivers",
    build_type=BuildType.STATIC.name,
    )
    # 也可以不写 defines,因为已经在 Drivers/NIMMAKE.toml 中声明

    # ── 依赖关系 ──
    core.DependOn(driver)

    # ── 目标 ──
    t = hlp.Program("test", sources=["startup_stm32f407xx.s"])
    hlp.DefaultTarget(t)

    # ── 命令 ──
    bin = hlp.Command("BIN", [
    f"{hlp['OBJCOPY']} -O binary BUILD/test.ELF build/test.bin",
    ])
    hlp.Phony("all", ["test", "BIN"])

    执行

    # 方式一:完整构建
    nimmake all

    # 方式二:单独构建某个 Party(需在 Nimmake.py 中指定命令行参数支持)
    # nimmake –party Driver


    6. 与 CMake 的对比

    CMake 的做法

    # CMakeLists.txt(中心化)
    add_library(driver STATIC
    Drivers/Src/stm32f4xx_hal_adc.c
    Drivers/Src/stm32f4xx_hal_dma.c
    Drivers/Src/stm32f4xx_hal_gpio.c
    # … 几十个文件
    )
    target_include_directories(driver PUBLIC Drivers/Inc)
    target_compile_definitions(driver PUBLIC STM32F407xx)

    add_executable(test
    Core/Src/main.c
    Core/Src/system_stm32f4xx.c
    startup_stm32f407xx.s
    )
    target_link_libraries(test driver)

    Nimmake 的做法

    分散到组件自身:

    # Drivers/NIMMAKE.toml(去中心化)
    [target]
    srcs = [
    "Src/stm32f4xx_hal_adc.c",
    "Src/stm32f4xx_hal_dma.c",
    # …
    ]
    incs = ["Inc"]
    defines = ["STM32F407xx="]

    # Nimmake.py(只关心依赖关系)
    driver = hlp.Parties("Driver", "Drivers")
    core.DependOn(driver)
    t = hlp.Program("test", sources=["startup_stm32f407xx.s"])

    关键对比维度

    维度CMakeNimmake (Party TOML)
    配置方式 中心化 CMakeLists.txt 分散化 NIMMAKE.toml 在每个组件目录
    源文件声明 在 CMakeLists.txt 中列出 在组件自己的 NIMMAKE.toml 中列出
    编译命令生成 CMake 生成 Makefile/Ninja Nimmake 直接生成 Ninja 或 Shell
    语法 CMake 专有语言 TOML(声明式)+ Python(逻辑)
    学习曲线 陡峭(CMake 语言特性多) 平缓(TOML 简单、Python 熟悉)
    增量构建 依赖 Make/Ninja 内置 Tracker + BuildCache
    文件扫描 file(GLOB …) 不推荐 自动发现 + TOML 声明双模
    跨平台 优秀(全平台支持) 良好(Windows/Linux/Mac)
    IDE 集成 优秀(CLion、VS、VSCode) 基础(命令行 + Ninja)

    详细对比

    6.1 源文件管理
    方面CMakeNimmake
    新增 .c 文件 修改 CMakeLists.txt 添加 target_sources() 修改 NIMMAKE.toml 在 srcs 列表追加
    删除 .c 文件 从 CMakeLists.txt 移除 从 NIMMAKE.toml 移除
    文件组织 按目标聚合(add_library) 按目录聚合(每个目录一个 TOML)
    GLOB 风险 file(GLOB) 不检测新文件 无 TOML 时自动发现;有 TOML 时精确列表
    6.2 依赖管理
    方面CMakeNimmake
    目标间依赖 target_link_libraries(A B) A.DependOn(B)
    传递性 PUBLIC/PRIVATE/INTERFACE 依赖被继承(源文件级)
    循环依赖 不允许 在 DependOn() 中可声明(由用户保证无环)
    外部库 find_package() third_party="HAL" 参数指定预设
    6.3 构建速度
    方面CMakeNimmake
    配置阶段 CMake 配置耗时 几乎零配置(直接执行 Python)
    构建阶段 生成器(Ninja 快,Make 慢) 内置 Ninja 生成 / Shell 模式
    增量检查 文件时间戳 Tracker(哈希 + 时间戳 + 编译命令变化)
    大型项目 优秀(Ninja 并行) 良好(当前单线程执行,可并行)
    6.4 代码简洁性

    CMake(中心化,20 个组件):

    # CMakeLists.txt
    add_subdirectory(drivers)
    add_subdirectory(core)
    add_subdirectory(freertos)
    add_subdirectory(lwip)
    add_subdirectory(fatfs)
    # … 20 个 add_subdirectory
    add_executable(firmware main.c startup.s)
    target_link_libraries(firmware drivers core freertos lwip fatfs …)

    Nimmake(去中心化,20 个组件):

    # Nimmake.py
    driver = hlp.Parties("Driver", "Drivers")
    core = hlp.Parties("CORE", "Core")
    freertos = hlp.Parties("FreeRTOS", "FreeRTOS", third_party="FREERTOS")
    lwip = hlp.Parties("LwIP", "LwIP", third_party="LWIP")

    core.DependOn(driver)
    core.DependOn(freertos)
    freertos.DependOn(lwip)

    t = hlp.Program("firmware", sources=["main.c", "startup.s"])
    hlp.DefaultTarget(t)

    每个组件的 NIMMAKE.toml 由组件作者维护,主脚本只关心依赖拓扑。

    6.5 Nimmake 的独特优势

    优势 1:组件自治 + 去中心化

    每个组件目录就是一个自描述的构建单元:

    # Components/Lvgl/NIMMAKE.toml
    [target]
    srcs = [
    "src/lv_core/lv_obj.c",
    "src/lv_draw/lv_draw_rect.c",
    # … 其他源文件
    ]
    incs = ["src"]
    defines = ["LV_CONF_PATH=\\"lv_conf.h\\""]

    组件复用 = 复制目录 + 在 Nimmake.py 中 hlp.Parties()。无需修改任何中心配置文件。

    优势 2:构建参数可编程

    Python 是图灵完备的语言,可以在构建脚本中做任意逻辑:

    for chip in ["STM32F407", "STM32F429", "STM32F103"]:
    cfg = get_chip_config(chip)
    hlp.Config(cfg)
    core = hlp.Parties("CORE", "Core") # 同一个 Core 目录
    # … 同时构建三个芯片版本

    CMake 虽然也支持循环,但语法远不如 Python 直观。

    优势 3:零外部依赖

    import tomllib # Python 3.11+ 内置
    # 无需安装任何包

    Nimmake + Python 标准库 = 完整构建系统。CMake 需要安装 CMake 二进制。

    优势 4:构建缓存内置

    CMake 依赖 Make/Ninja 的增量构建。Nimmake 内置 [Tracker](file:///d:/MyCodeNew/nimmake/src/nimmake/builders/Tracker.py) 和 [BuildCache](file:///d:/MyCodeNew/nimmake/src/nimmake/builders/buildCache.py#L50-L113):

    • 哈希比较编译命令是否变化
    • 时间戳比较源文件是否更新
    • 编译命令变化时自动重新编译
    • 缓存命中时零耗时跳过

    6.6 CMake 的相对优势

    优势说明
    生态成熟 CMake 有 20+ 年历史,无数第三方模块
    IDE 支持 CLion、VS、VSCode 原生支持 CMake
    包管理器 find_package() + Conan/vcpkg 集成
    测试框架 enable_testing() + add_test() 内置
    安装规则 install() 命令成熟
    全平台 Windows/MSVC、Linux/GCC、Mac/Xcode 全支持
    语言标准 支持几乎全部编译器和语言

    6.7 适用场景建议

    场景推荐方案
    嵌入式 MCU 项目(STM32/ESP32 等) Nimmake(轻量、组件自治、零依赖)
    大型桌面/服务器应用 CMake(生态成熟、工具链全)
    快速原型、个人项目 Nimmake(配置简单、上手快)
    团队协作、企业级项目 CMake(IDE 支持好,新人熟悉)
    混合方案 CMake 构建主程序,Nimmake 管理 MCU 固件

    7. 最佳实践

    ✅ 推荐做法

    1. 为稳定的组件编写 TOML

    # ✅ 推荐:稳定的大库用 TOML
    # Drivers/NIMMAKE.toml(HAL 库很少改动)
    srcs = ["Src/stm32f4xx_hal_adc.c", "Src/stm32f4xx_hal_dma.c", …]

    # ❌ 不必要:频繁改动的文件不需要 TOML
    # App/NIMMAKE.toml(每天都在改)
    srcs = ["app.c"] # 只有一个文件,自动发现即可

    2. 自动发现 + TOML 混合使用

    # 大库用 TOML 声明(跳过目录扫描,加快构建)
    driver = hlp.Parties("Driver", "Drivers", build_type=BuildType.STATIC.name)
    # 小库自动发现(无需 TOML)
    app = hlp.Parties("APP", "App")

    3. 利用 TOML 生成命令导出配置

    nimmake –party Core
    # 自动生成:
    # [target]
    # srcs = […]
    # incs = […]

    该功能由 [__toml_party()](file:///d:/MyCodeNew/nimmake/src/nimmake/builders/parseParties.py#L722-L743) 实现,自动将目录扫描结果输出为 TOML 格式,复制到文件即可。

    4. 保持路径简洁

    # ✅ 推荐:相对于 TOML 目录
    srcs = ["Src/main.c"]

    # ❌ 不推荐:绝对路径
    srcs = ["/absolute/path/to/Src/main.c"]

    # ❌ 不推荐:相对项目根
    srcs = ["../../Core/Src/main.c"]

    5. 使用注释模板

    # 保留注释模板,方便快速启用
    # defines = ["DEBUG=1", "USE_HAL_DRIVER=1"]
    # include_macros = ["BOARD_ID=345"]

    ⚠️ 注意事项

    问题说明解决方案
    TOML 不存在 setup() 自动回退到目录扫描,无报错 可选择性使用
    路径格式 Windows 使用 / 而非 \\ TOML 中使用 "Src/main.c"
    宏格式 defines 使用 "KEY=VALUE" 字符串 参考示例:"STM32F407xx=" 表示纯定义
    d 后缀 "STM32F407xx=" 会被解析为 {"STM32F407xx": ""} → -DSTM32F407xx 留空值在 = 后即可
    TOML 优先级 TOML 中的 defines 会与 hlp.Parties(defines=…) 中的合并 两者同名时以 hlp.Parties() 为准

    总结

    Party TOML 配置体系是 Nimmake 实现"组件自治"构建的核心机制:

    特性说明
    声明式 组件自己声明源文件/头文件/宏
    去中心化 每个目录一个 NIMMAKE.toml
    高性能 跳过目录扫描,直接读取文件列表
    可复现 源文件列表版本化,CI 一致
    零依赖 Python 3.11+ 内置 tomllib

    与 CMake 的本质区别:

    CMake 是中心化的——一个 CMakeLists.txt 统治所有组件; Nimmake 是去中心化的——每个组件有自己的 NIMMAKE.toml,主脚本只编排依赖。

    对于 嵌入式 MCU 项目,Nimmake 的 Party TOML 方案提供了更轻量、更直观、更容易维护的构建体验。

    赞(0)
    未经允许不得转载:171主机测评 » Party TOML 配置体系:通过 `NIMMAKE.toml` 实现组件级声明式构建
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址