ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

70 个 CMakeLists.txt 怎么管?Aether 的构建架构值得抄

2026/8/23 18:50:00 拓冰建站 浏览量
70 个 CMakeLists.txt 怎么管?Aether 的构建架构值得抄 CMake 实战续集从能编译到优雅构建一、先讲个真实的崩溃朋友接手一个项目打开工程目录git clone然后mkdir build cmake ..。boom。报了一百多个错。然后他发现代码里手动set(SOURCES ...)列了两百个 .cpp漏了几个链接失败每个插件目录都有一个一模一样的 CMakeLists.txt复制粘贴了十几遍想关掉某个模块得手动去三个文件里改注释输出目录乱七八糟DLL 到处乱扔他发消息问我“你们的项目 70 多个 CMakeLists.txt是怎么管住的”我打开 Aether 的根 CMakeLists.txt从头到尾给他讲了一遍。这篇就是那天的内容。二、70 个 CMakeLists.txt 的分层架构Aether 的 CMake 不是一个文件管所有的平铺结构也不是每层各自为政的散装结构。它是三层职责分离CMakeLists.txt根 ├─ 全局编译选项 / C标准 / Qt版本 ├─ Qt AUTOMOC / AUTORCC ├─ MSVC 警告屏蔽 └─ add_subdirectory(common) add_subdirectory(app) add_subdirectory(plugins) common/CMakeLists.txt公共库层 ├─ include(FZ2Helpers) include(CommonOptions) ├─ add_subdirectory(core) / ui / utility / base / plugins └─ 每个子模块独立编译为 STATIC 或 SHARED 库 plugins/CMakeLists.txt插件层 ├─ include(PluginOptions) ├─ 每个插件按 PLUGIN_XXX_BUILD 开关条件 add_subdirectory └─ 每个插件的 CMakeLists.txt 不到 40 行 每级 CMakeLists模块级 └─ fz2_collect_sources → add_library → fz2_setup_target → target_link_libraries每层的职责完全不同层做什么不做什么根全局标准、Qt查找、MSVC选项、子目录编排不列源文件、不写细节编译选项公共库层汇总模块、选项系统、加载 CMake 函数库不控制插件开关插件层按业务开关决定哪个插件参编不设全局编译标准模块级收集源文件、编译、链接不设全局 flag这个分层的好处很直接。改全局设置只动根 CMakeLists.txt不会影响任何插件。加一个新插件只在自己的目录下新增一个 CMakeLists.txt用 fz2_setup_plugin 配置输出目录不污染别的地方。关掉一个模块改一行 option 就行不需要改 cmake 文件本身。70 多个 CMakeLists.txt 看着多但每一级都是有限模板 少量差异化。真正需要手写逻辑的地方每个文件不超过 40 行。三、FZ2Helpers把 70% 的重复代码抽掉如果每个 CMakeLists.txt 都写一堆set_target_properties、file(GLOB ...)、source_group(...)那 70 个文件就变成了 70 座屎山。Aether 的做法是把重复模式抽成函数。CommonOptions.cmake 和 FZ2Helpers.cmake 放在common/cmake/下一 include全项目都能用。来看看这几个函数怎么把 10 行 CMake 变成 1 行。3.1 输出目录10 行 → 1 行没有 fz2_setup_target 之前每个 CMakeLists.txt 都要写# ❌ 没有辅助函数的日子 set_target_properties(myapp PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/RelWithDebInfo LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/RelWithDebInfo )现在只用一行# ✅ 一行搞定 fz2_setup_target(myapp app) # exe → bin/Config/ fz2_setup_plugin(motion_control plugins/motion) # DLL → bin/Config/plugins/函数的实现不复杂就是把那堆set_target_properties包起来。但 70 个目标各省 9 行就是 600 多行的差距。插件还有特殊的fz2_setup_plugin——它的输出目录多一个/plugins子目录并且会把 DLL 的PREFIX置空。Windows 下不会出现libCamera.dll直接就是Camera.dll。3.2 源文件收集再也不用维护文件列表手工维护set(SOURCES a.cpp b.cpp c.cpp ...)是 CMake 最坑的地方。加一个文件漏写一行编译就过不了。# ❌ 手动维护必漏 set(SOURCES camera_service.cpp # 新加了一个 file_utils.cpp忘了加在这 camera_control.cpp image_processor.cpp # 漏了 file_utils.cpp —— 编译报错排查 10 分钟 )# ✅ 自动收集一个函数搞定 fz2_collect_sources(PLUGIN_SOURCES PLUGIN_HEADERS) # 自动扫 *.cpp *.cc *.h *.hpp排除 tests/ examples/ 目录fz2_collect_sources用的是file(GLOB_RECURSE ... CONFIGURE_DEPENDS)配合CONFIGURE_DEPENDS参数新增文件时 CMake 会自动重新配置。但注意如果同一个 target 里有些 .cpp 要根据条件编译比如运控驱动实际现场只选一个后端这时候不能用自动收集。得手动维护编译列表另用fz2_expand_tree_files补充 IDE 展示用的头文件列表。3.3 VS 解决方案目录树Windows 团队用 Visual Studio 开发没有source_group的话所有源文件在 VS 里全平铺在源文件目录下找文件靠翻。# ✅ 按目录结构在 VS 里展示 fz2_target_source_group_tree(Camera camera FILES ${PLUGIN_SOURCES} ${PLUGIN_HEADERS} )这样 VS 解决方案资源管理器里看到的就是和文件系统一致的分层目录。3.4 编译翻译文件Qt 项目的.ts → .qm翻译编译一般都手动跑lrelease。Aether 把它挂到 CMake 里构建后自动执行# ✅ 自动编译翻译文件 fz2_target_compile_app_translations(Aether ${CMAKE_SOURCE_DIR}/config app_zh_CN.ts;app_en_US.ts )构建完直接运行不用再操心翻译文件没更新。3.5 函数清单一览函数用途省掉的工作fz2_setup_target设置 exe/lib 输出目录每个 target 9 行set_target_propertiesfz2_setup_plugin设置插件 DLL 输出目录 去前缀同上 PREFIX 处理fz2_collect_sources自动收集 .cpp/.h维护源文件列表fz2_expand_tree_files展开 IDE 树文件列表手动补充头文件fz2_source_group_treeVS 目录树展示手工 source_groupfz2_target_compile_translations自动编译 .ts → .qm手动跑 lreleasefz2_remove_disabled_plugin_artifacts清理残留 DLL手动删除7 个函数覆盖了 CMakeLists.txt 里 70% 的重复代码。四、选项系统一个开关自动传遍全家大项目都有这个问题某个模块关了依赖它的示例程序、测试、联合编译要不要也关手动维护这种依赖链会疯掉。Aether 的方案是option() 派生开关 自动门控。4.1 基础 option每个可选的编译单元都有一个option# CommonOptions.cmake # ── 硬件驱动开关用户可调── option(HW_CAMERA_GALAXY_BUILD 编译 Galaxy 相机库 ON) option(HW_CAMERA_TOUP_BUILD 编译 Toupcam 相机库 ON) option(HW_CAMERA_VIRTUAL_BUILD 编译虚拟相机库 ON) option(HW_CAMERA_MV_BUILD 编译 MVS 相机库需厂商 SDK ON) # ── 示例程序开关用户可调── option(DEMO_CAMERA_GALAXY_EXAMPLE_BUILD 编译 Galaxy 相机示例 ON)4.2 派生开关自动求或如果四个相机库里至少开了一个才需要编译相机抽象层。不用手写逻辑用代码算# 只要有任何一个相机库编译相机抽象层就打开 if(HW_CAMERA_GALAXY_BUILD OR HW_CAMERA_TOUP_BUILD OR HW_CAMERA_VIRTUAL_BUILD OR HW_CAMERA_MV_BUILD) set(HW_CAMERA_ANY_BUILD TRUE) else() set(HW_CAMERA_ANY_BUILD FALSE) endif()4.3 自动门控依赖模块关了的示例强制关最绝的是这个_fz2_gate_demo_if_off函数# 核心逻辑 function(_fz2_gate_demo_if_off DEMO_VAR HW_VAR REASON) if(NOT ${HW_VAR}) if(${DEMO_VAR}) message(STATUS ${DEMO_VAR} → OFF${REASON}) endif() set(${DEMO_VAR} OFF CACHE BOOL FORCE) endif() endfunction() # 批量应用 _fz2_gate_demo_if_off(DEMO_CAMERA_GALAXY_EXAMPLE_BUILD HW_CAMERA_GALAXY_BUILD Galaxy 相机未配置) _fz2_gate_demo_if_off(DEMO_CAMERA_TOUP_EXAMPLE_BUILD HW_CAMERA_TOUP_BUILD Toupcam 相机未配置) _fz2_gate_demo_if_off(DEMO_DEVICE_MOTION_EXAMPLE_BUILD HW_MOTION_CONTROL_BUILD 无运控卡后端开启)效果用户只在最上层改一个option相机抽象层自动停掉所有相机相关的示例和测试也全部自动关闭不会出现编译到一半报缺少依赖的错误。4.4 实际使用体验# 只需要关一个剩下的自动搞定cmake..-DHW_CAMERA_MV_BUILDOFF# 输出示例# -- DEMO_CAMERA_MV_EXAMPLE_BUILD → OFFMVS 相机未配置# -- Camera 抽象层 → OFF无任何相机后端# -- 跳过 MVS 相机示例编译# -- 跳过 CameraPlugin 编译这就是改一处全家生效的选项系统。没有这种设计改个配置要在 CMakeLists.txt、代码宏、CI 脚本三个地方同步修改早晚漏一个。五、CMake 系列读者的进阶衔接Aether 的 CMake 架构不是凭空想出来的。如果你看过 CMake 实战系列的前 8 篇你会认出这里面的每一个元素现代 CMake 的目标导向设计——每个target自己管自己的编译选项和链接库不污染全局target_include_directories的 PRIVATE / PUBLIC / INTERFACE——extensionsystem的 PUBLIC include 路径自动传给所有链接者生成器表达式——$CONFIG:Debug、$TARGET_FILE_DIR:...随处可见module 模式——FZ2Helpers.cmake作为 cmake 模块被include而不是一个被包含的普通文件知识是知识实战是实战。70 多个 CMakeLists.txt 不是让你背指令的是让你看模式的把重复变成函数把依赖变成自动计算把配置变成一层管一层。没有看过 CMake 实战系列的朋友这里有个快速路线篇目核心内容对应本节现代 CMake 的目标导向target 是构建的第一公民fz2_setup_targetPRIVATE / PUBLIC / INTERFACE可见性控制target_link_libraries 设计生成器表达式构建时的条件判断$CONFIG:Debug用法CMake 模块化用 include 组织函数库FZ2Helpers.cmake但说实话读 8 篇教程不如读一遍 Aether 的 CMakeLists.txt。文件都在common/cmake/下加起来不到 700 行读完你就知道工业级 CMake长什么样。六、写在最后回到开头那个朋友的问题“70 多个 CMakeLists.txt怎么管”答案是不管。让架构自己管。分层设计——每层只写自己该写的函数抽象——7 个辅助函数干掉 70% 重复代码自动传播——开关改了依赖链自己算每级瘦身——单个 CMakeLists.txt 不超过 40 行把 70 个文件拆成有限模板 少量差异化这 70 个就不多了。它们只是 70 个相同的模式各自带了一点点不同的源文件列表而已。 评论区聊聊你的项目 CMake 多少行评论区说说你 CMake 最大的痛点——是依赖管理是源文件列表维护还是跨平台编译我挑几个典型问题下篇展开。 觉得有用转发给正在为 CMakeLists.txt 头疼的朋友。一个项目 70 个 CMakeLists.txt 不可怕可怕的是没有架构地写 70 个。⭐ 觉得不错点个在看支持一下。 下期预告下一篇Qt 与 CMake 的黄金搭配。find_package(Qt6)谁都会写但怎么组织 QRC 资源、怎么自动部署 Qt DLL、怎么在 CI 里加速 Qt 编译这些才是真功夫。我们下周见。