不少C++开发者看到“XMake”这个名字,第一反应是又一个折腾构建的工具,第二反应是它凭啥想去碰CMake的位置。我最初也这么想,直到自己用CMake配一个新项目,从工程生成到跑通第三方库花了一个多小时,而同样的需求用XMake大概只花了十分钟。这个差距让我不得不认真回头看这个“后来者”。
今天这篇不是要给出一个非黑即白的结论,而是从这十年C++构建系统的演变讲起,把CMake为什么能统治、XMake为什么有底气挑战、以及所谓的“取代”到底卡在哪里,一层层拆开揉碎。文章里会有两份真实可跑的工程配置做对比,也会聊到生态、迁移成本、IDE支持这些CMake教程里不常提的软性因素。适合被CMake折腾过的人、正在做技术选型的团队,以及所有想搞清楚C++构建系统现状的开发者。
1. C++构建这十年:CMake怎么赢的,又为什么让人觉得“该变了”
1.1 CMake当年赢在哪:让跨平台构建从噩梦变成“能忍”
无论在XMake的话题下吵得多凶,有一点得承认:CMake解决的问题是实打实的。十年前乃至更早,C++项目跨平台构建基本是场灾难。Linux下有autotools那套,Windows上是Visual Studio的.sln工程,macOS又有自己的Xcode project,每套体系互不兼容。想在Linux上开发一个库让Windows同事也能用,你得维护至少两套构建脚本,再加上不同编译器之间的差异,光是配置环境就能耗掉半天。
CMake的核心思路是做“生成器”:你写一份CMakeLists.txt,它根据当前平台和工具链生成对应的原生工程文件,Linux下给你Makefile,Windows下给你Visual Studio工程,macOS下给你Xcode工程。这个抽象层在当年是革命性的。开发者只需要描述“项目里有哪些源文件、链接哪些库”,剩下的跨平台工作交给CMake。再加上它提供了find_package这种查找第三方库的机制,和CTest、CPack等配套工具,逐渐把构建、测试、打包串成了一条完整链路。
到现在,绝大多数主流C++开源库的构建首选项都是CMake,GitHub上随便搜一个C++项目,十个里有八个带CMakeLists.txt。这意味着如果你用CMake,你几乎不会遇到“这个库不支持你的构建系统”的尴尬。这种生态护城河,不是靠一两句技术口号能填平的。
1.2 但CMake的语法和心智负担,是实实在在的坑
如果只看功能覆盖,CMake几乎是完美的。但真正上手写CMakeLists.txt的人,十个里有九个会吐槽它的语法。举个例子,CMake的变量作用域、函数参数传递、字符串列表的引号规则,这些细节每一个都曾让新人在网络社区里发帖求助。网上搜“cmake教程”的人可能比搜“c++ lambda用法”的还多,这本身就说明问题。
更让人头疼的是CMake的“新老写法并存”问题。早期版本提倡的方式和现代CMake推荐的target-based方式差异巨大,你在网上搜到的很多CMake写法还停留在十几年前。我自己接过一个老项目,里面的CMakeLists用add_definitions、include_directories、link_directories这种全局函数,升级新版本后虽然还能跑,但警告刷屏,维护起来非常痛苦。再往深了说,CMake的字符串处理、循环、条件判断都很笨拙,想在里面写稍微复杂一点的逻辑,体验远不如一门真正的脚本语言。
还有一个常见痛点:CMake生成Visual Studio工程时,默认会把一些路径写死,这也是“cmake生成的vs工程使用相对路径的写法”这类搜索长期居高不下的原因。解决方案有,但分散在不同版本的文档里,新手根本找不到。每次都要靠老手经验解决,这本身就是一种高昂的心智负担。
1.3 变局的开端:当CMake不再是唯一合理的默认选择
最近五年,C++构建领域其实一直有新玩家冒出来,最典型的是Meson和XMake。Meson主打极快的配置速度和更简洁的语法,在GNOME社区和不少Linux桌面项目里站稳了脚跟。XMake则走了一条不同的路——它用Lua作为底层脚本语言,强调零配置、开箱即用、跨平台通吃,内置工具链检测和包管理能力。
为什么这些新工具能冒头?因为CMake的“能用”不等于“好用”,当C++项目的复杂度持续上升,开发者对构建系统的效率、可读性、可调试性的要求也跟着提高。现代C++项目不仅要管编译链接,还要管依赖下载、代码生成、多配置切换、交叉编译,这些需求叠在CMake这棵老树上,解决方式往往是补丁摞补丁。XMake这类新工具的直接目标,就是把这些现代需求从第一天就做进设计里。
不过,“挑战者出现”和“位置被取代”是两码事,这也是这篇文章真正想聊清楚的地方。
2. XMake的核心差异:用Lua写构建,而不是“换个方式写CMake”
2.1 描述语言之争:CMake自创语法 vs Lua本身就是强脚本语言
XMake最显眼的差异化设计,是直接用Lua作为描述语言。这个选择有个容易被忽略但很关键的好处:Lua是图灵完备的编程语言。CMake的语法虽然也图灵完备,但用起来极其别扭;而Lua本身有清晰的变量作用域、函数、表、闭包等语言特性,任何熟悉编程的人几乎零成本上手。
我拿一个真实场景来说。在CMake里遍历目录下的所有源文件,常规做法是:
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "src/*.cpp")这个写法看起来不复杂,但GLOB有一个著名的坑:新加文件后CMake不会自动感知,必须重新运行CMake才能刷新文件列表。社区里对这个问题的讨论持续了很多年,结论基本是“别依赖GLOB,手动列出文件最可靠”。可手动列出几十个文件,维护成本又上来了。
XMake里对应写法是:
add_files("src/*.cpp")同样是通配符,XMake的规则是每次构建时按需检查文件变化,不需要额外配置。这个差异看着小,实际用起来能省不少心。更重要的是,当项目逻辑开始复杂——比如需要根据构建参数拼接不同的源文件列表、需要生成代码后才编译特定目录,用Lua写逻辑就像在写普通程序,而不是在猜CMake的函数签名。
2.2 内置工具链检测和包管理:省掉半天的配置时间
用CMake配置一个新项目时,最劝退的是环境准备。你得确认编译器有没有装、路径对不对,用find_package找库时偶尔还会因为找不到库路径卡住,然后在CACHE变量、toolchain文件之间来回折腾。
XMake的目标恰恰是干掉这些步骤。它内置了对常见编译器(MSVC、GCC、Clang、MinGW等)的自动检测能力,你只需要执行xmake f --toolchain=clang这种命令,它自己会去扫描系统环境,定位编译器路径并生成对应配置。我第一次用的时候,下载了XMake后直接跑xmake create新建工程,然后xmake构建,前后不到两分钟,一个可执行文件就出来了——没有手写一行CMake。
依赖管理上XMake也做得很直接:
add_requires("libcurl 7.80.0") target("demo") set_kind("binary") add_files("src/*.cpp") add_packages("libcurl")第一行声明需要libcurl的指定版本,XMake会去它的包仓库下载并编译,目标里用add_packages链接进来。整个过程不需要手动执行git clone、cmake、make这些步骤,也不需要在系统里预先安装开发包。对比CMake的方式:
find_package(CURL REQUIRED) target_link_libraries(demo PRIVATE CURL::libcurl)这行find_package在Linux上大概率能跑通,但前提是你系统里装好了libcurl开发头文件;到了Windows,先得折腾vcpkg或手动指定路径;跨平台配置文件的差异越来越多时,维护成本就呈指数上升。XMake把“拉依赖”和“用依赖”做进同一个工具链,这种体验在C++构建工具里确实算是往前迈了一大步。
2.3 零基础快速上手:xmake四步从零到跑通
如果你是第一次接触XMake,我把最基础的一条路走一遍。前提是装了XMake本体(这个很简单,官网下载或包管理器都可以),以及一个常见C++编译器。
# 第一步:创建项目骨架 xmake create -l c++ -P mydemo # 第二步:进入项目目录 cd mydemo # 第三步:配置(这里会做工具链检测,不指定时自动选择) xmake f # 第四步:构建并运行 xmake xmake run第一次跑的时候,xmake f会把当前环境的编译器、链接器、系统架构、平台信息都自动探测一遍,输出一个summary。如果你的机器上同时装了MSVC和MinGW,可以用xmake f --toolchain=mingw这样的参数指定。整个过程不需要你手动创建build目录,不需要指定生成器名称,也不需要担心“要不要用Ninja”。
如果你是在VSCode里做C/C++开发,XMake也有官方插件支持。装好插件后,直接用VSCode打开包含xmake.lua的目录,插件会自动识别项目并带来任务列表、调试配置生成等能力,体验和CMake插件在配置良好的情况下接近,但省去了写tasks.json和launch.json的繁琐步骤。对配置C/C++环境头痛的新手来说,这条路友好得多。
2.4 一张表看懂CMake和XMake的核心对照
从这套对照就能直接看出,XMake在不少维度上确实是“自带干粮”的改进版。但写到这里也要诚实地说一句:工具“更好用”和“能不能取代”之间,隔着一整个生态系统的距离。
| 对比维度 | CMake | XMake |
|---|---|---|
| 描述语言 | 自创CMake语法 | Lua |
| 依赖管理 | 外部工具+vcpkg/conan等组合 | 内置add_requires |
| 工具链检测 | 需手写或依赖CMake自带的检测模块 | 内置自动检测 |
| 新项目上手速度 | 需要写CMakeLists并熟悉函数 | 四步命令直接跑通 |
| 跨平台能力 | 极强,主流平台全覆盖 | 较强,持续完善中 |
| 社区生态规模 | 极庞大 | 增长中,但差距明显 |
| 大型项目成熟度 | 经过数十年验证 | 快节奏项目中口碑上升 |
3. 一个真实项目的双写对比:同一份需求,两套构建脚本
3.1 场景设定:带第三方依赖的命令行工具
为了把对比落到实处,我搭一个接近日常工作的小项目:一个命令行工具,能从指定的URL下载文件,并把下载进度打印出来。功能不复杂,但需要有第三方依赖(HTTP客户端)、需要多平台可编译、需要区分Debug和Release配置,还得在构建结束后把可执行文件拷到指定的输出目录。
这个场景同时覆盖了:依赖引入、配置文件组织、多配置切换、自定义构建步骤。很典型,适合用来对比两套构建系统的实际体验。
3.2 CMake实现:能跑,但要写的东西确实多
先看CMakeLists.txt的完整版本:
cmake_minimum_required(VERSION 3.20) project(filedownloader CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 引入libcurl find_package(CURL REQUIRED) if(MSVC) # Windows下默认不启用UTF-8,这里加上避免转义中文乱码 add_compile_options(/utf-8) # 输出路径整理到build/bin目录 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) endif() add_executable(filedownloader src/main.cpp) # 链接依赖 target_link_libraries(filedownloader PRIVATE CURL::libcurl) # 区分Debug和Release的编译选项 target_compile_options(filedownloader PRIVATE $<$<CONFIG:Debug>:-g;-O0> $<$<CONFIG:Release>:-O3> ) # 自定义构建后步骤:把生成的可执行文件复制到指定目录 add_custom_command(TARGET filedownloader POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${CMAKE_SOURCE_DIR}/dist/ )这段代码写起来还不算最糟,但几个地方需要注意:如果libcurl没装,find_package直接报错,你得先确保开发环境就绪;MSVC版本下/window下运行时如果缺少依赖还要额外处理;复制目录那段在Windows和Linux下行为也有差异,需要测试确认。还有同事经常踩的坑:Debug版和Release版输出文件名一样,切换配置时可能互相覆盖,要用VS那套生成路径规则配合才稳妥。
“cmake生成vs工程使用相对路径的写法”这类困扰,正是从这种多配置环境里冒出来的。因为CMake对Visual Studio生成器默认使用绝对路径来引用中间目录和输出目录,一旦整个工程目录移动,旧工程还带着旧路径,重新加载也可能报路径错。解决方案不是没有,但要额外加配置,很多人根本不知道。
另外,如果要求Release模式下生成PDB文件用于后续问题排查,还得额外控制编译器参数加上/Zi和/DEBUG,属于那种“不看说明完全猜不到”的经典难点。“cmake输出路径去掉debug”这类搜索从另一个侧面说明,Debug文件夹嵌套导致脚本扫描路径时反复抽风,多少人被它坑过。
3.3 XMake实现:直观到像是“描述需求”而不是“琢磨指令”
同样的需求,XMake的xmake.lua是这样一个文件:
set_project("filedownloader") set_version("1.0.0") set_xmakever("2.8.0") add_rules("mode.debug", "mode.release") add_requires("libcurl") target("filedownloader") set_kind("binary") add_includedirs("src") add_files("src/*.cpp") add_packages("libcurl") -- 复制生成文件到dist目录 after_build(function(target) os.cp(target:targetfile(), "dist/") end)和CMake版对比,差异很直观。XMake里的add_rules("mode.debug", "mode.release")内置了两种模式模板,自动帮你把优化参数选好;add_requires自动下载并编译依赖版本,不需要你提前在系统里装什么;after_build接收一个Lua函数,函数内直接调用os.cp就能完成复制目标准备的输出。
更难得的是,Lua函数里的target:targetfile()拿到的是当前构建目标的可执行文件完整路径,也就是说,Debug模式和Release模式下会自动对应各自输出目录,不用像CMake那样手动折腾构建路径规则。这种“默认情况正好就是你想要的场景”的设计哲学,贯穿XMake的整个使用体验。
3.4 两边都跑通之后,最大的差距其实是“改起来的心态”
两套方案都成功配置并跑通后,我最大的感触不在第一次构建的速度,而在后续改动时的心态。
CMake基础功扎实的人自然能应付大多数场景,但遇到前面说的“路径写死”“PDB没生成”“预编译头怎么加”这类问题,动不动就得开文档、翻Stack Overflow,搜索关键词还可能踩到过时写法的坑。改一个变量或者加一个新的第三方库,生怕动到了什么全局配置导致其他目标出幺蛾子。
XMake的Lua脚本更接近普通程序员的直觉。遇到问题,你直接在xmake.lua里print调试、写个条件判断、调一个API,都是正常编程思维。它不需要你学习“CMake式的思维模式”——那种反复琢磨变量作用域、生成器表达式的状态,本身就消耗很大的注意力。把构建配置当成代码一样写,这一点的体验差距,比很多纯技术指标对比更深刻。
4. 为什么“取代”没那么简单:生态、迁移与团队惯性
4.1 生态规模:开源库的CMake适配,已经深到骨头里
软件生态有一个经典的“网络效应”逻辑:一个工具用的人越多,围绕它的第三方支持和沉淀就越多,后来者想取代,门槛远远超出“技术方案更优”本身。
放到构建系统这个语境里,生态最关键的表现是:几乎所有知名C++开源库都给你准备好了CMake支持。你能想到的:OpenCV、Boost、Abseil、curl、fmt、spdlog、jsoncpp……它们的主构建体系要么是CMake,要么能通过CMake Find模块或config模式被无缝集成。这意味着你使用CMake时,默认默认就是“所有轮子都给你配好了”,很多团队的项目能够快速搭建,就是踩在前人铺好的CMake兼容层上。
而XMake的生态,尽管已经有了相当多的包支持,但目前覆盖面还在持续追赶阶段。你用add_requires("libcurl")这种成熟库当然毫无压力,可一旦碰上冷门一点的库,去查xmake repo看有没有收录,很可能查不到,或者只有老版本。这时候你就得回到那条老路:自己写构建接入。正是这类“不在默认舒服区”的小场景汇聚起来,构成了XMake走向普及的现实阻力。
4.2 迁移成本:老项目不是说换就能换的
即使XMake在语法和体验上优于CMake,对一个已经用CMake维护了五年以上的项目来说,把上百个CMakeLists.txt和无数find_package逻辑改用XMake重写,迁移成本谁买单?
你得把现有构建配置翻译成Lua,处理带有各种CMake历史包袱的逻辑,目标平台之间的特殊处理都要重新验证,新引入的包语法和依赖方式也可能改变整个团队的构建习惯。开发团队的效率短时间大概率是下降的,而构建系统的收益是被长期稀释的——除非团队本来就要从零搭建新项目,否则多数人不会主动承担这种切换带来的短期痛苦。
我不是在劝大家不要迁,而是说迁移构建系统这件事,本质上不是技术选型,是成本决策。很多人喜欢XMake,可能只是新项目里试用,不会动老项目的主构建。
4.3 人才池与IDE集成:容易被忽略的“软性护城河”
很多人聊到技术选型,只盯着硬指标,很少考虑一个更现实的问题:团队里有几个人精通这个工具?
CMake的“人才池”其实是很稳固的。哪怕一个新入职的同事从没写过CMake,市面上教程和旧代码示范太多,边做边学也不至于卡死。XMake的社区虽然在成长,但至少目前写XMake的人数量级远小于CMake。一旦项目遇到一个冷门问题,搜索相关帖子,有答案的几率也相对低——这对于怕花时间解决未知问题的团队来说,是实打实的选型顾虑。
IDE集成方面,CMake拥有VS Code、CLion、Visual Studio、Qt Creator、甚至Xcode生态的成熟插件,支持语义高亮、调试配置、目标切换等功能,这些插件经过多年打磨,稳定性相当高。XMake的IDE插件虽然也在快速迭代,VS Code里基本能用,CLion也有第三方支持方案,但距离CMake插件的成熟度还是有差距。对依赖IDE构建流程的团队来说,这个差距会直接影响切换意愿。
4.4 XMake的真正短板:文档、长期维护与社区深度
把XMake短期内的缺点摆到台面上:它的官方文档相对精简,遇到边缘场景时,可参考内容不如CMake丰富。CMake的Wiki、Stack Overflow回答、书籍、视频,数量级碾压。很多刁钻问题的答案只能靠官方Issues里的讨论,或者读源码才能定位。
长期维护的风险也要考虑。CMake背后有Kitware公司持续投入,属于商业组织维护的基础设施型项目,大厂和科研机构都在用。XMake目前的维护力量以开源团队为主,活跃程度其实不错,但你得权衡:如果社区的活跃度下降,你会不会被困在一个开发节奏放缓的自建工具上?这类风险对技术负责人来说非常现实。
再加上“cmake可以代替keil5吗”这种嵌入式场景的热搜,侧面说明在单片机、嵌入式领域,构建系统的选择更保守。毕竟MCU开发里工具链和IDE绑定很深,芯片厂商原厂支持的构建方式大概率是自家的工程或专用IDE,CMake都不一定能完全顶替,XMake想切入这块需要更长时间教育市场。
5. 那到底该不该切?我给个不打太极的结论
5.1 场景判断:什么情况下XMake是你的更优解
聊了这么多,总得要落到“怎么办”。如果做决定的是你个人、小团队或者合作型新项目,我认为以下几个场景里XMake是值得尝试的:
- 你从零搭建一个全新C++项目,不需要兼容历史包袱。
- 项目对第三方依赖要求不高,以标准库或成熟高频库为主。
- 团队里有熟悉Lua或者乐意接受新工具的成员,愿意一起推进构建演进。
- 经常需要跨平台交叉编译(Windows/Linux/macOS/Android/iOS等),希望用更统一的配置方式来管理。
- 你本来就不喜欢“CMake式的思维”,更希望构建脚本能像普通代码一样方便调试和逻辑处理。
在这些情况下,XMake能够给到的体验提升是相当显著的。我在实际使用中,最大感受是它可以让我把省下来的时间用在写业务逻辑上,而不是反复折腾构建文件。
5.2 什么人暂时留在CMake更稳妥
反过来,如果你的项目规模庞大、生态依赖众多、团队构成复杂,我肯定不建议立刻切XMake:
- 项目里第三方库非常多,且大量依赖CMake生态的find_package和config模式对接。
- 团队里多数人已经对CMake驾轻就熟,短时间内培养对XMake的熟练度成本不低。
- 项目需要和特定的CI/CD或IDE体系深度集成,而这些体系目前只对CMake支持最成熟。
- 你接手的是老牌开源库,贡献者社区对构建系统的改造非常敏感,擅自切换会引发大量讨论和阻力。
这种情况下,继续用CMake不等于落后,反而是把风险降到最低的成熟选择。
5.3 如何让两个体系共存:渐进式混用建议
如果项目太大没法一步到位迁移,还有一个更平滑的思路:让两个体系共存。你保留现有CMake配置作为主构建体系,同时在一两个新模块或新工具里试用XMake,分别构建成独立产物。这样既不会影响整体交付,又能以较低成本验证XMake是否真的适合你们团队。
从实际操作来看,渐进式接受一个新技术,比“推翻重来”更合情理,团队心态也更放松。关键是跟进试用过程中发现的坑,记录文档化的使用笔记,这样等你想全面切换时,已经有了可复用的经验沉淀,而不是裸奔着上新工具。
6. 几年用下来的体会,最后分享两点
构建工具的选择,从来不只是功能对比表上的分数,它更是一个组织长期沉淀出来的习惯和肌肉记忆。我自己经历过从Makefile到CMake再到尝试XMake的过程,最大的体会是:工具越顺手的底层逻辑,越在于它是不是贴合“人的直觉”。CMake承载了太多历史包袱,而XMake的轻巧设计恰好缓解了这种需求。
最后分享几个在日常操作中的小技巧,结合网上常见的那些疑惑,算是给大家顺手填坑:
- 用CMake的VS多配置生成器时,别老想着全局去改输出路径,优先用生成器表达式按目标处理。Release模式没帮你生成PDB,也可以在target_compile_options里传/Zi配合链接器/DEBUG解决,这些都是正经微软系前端的常识路径。
- XMake首次跑xmake f时如果自动选错编译器,不用慌,手动xmake f --toolchain=clang重新配置即可,它会覆盖旧的探测结果。
- 在XMake里想执行外部命令,比如调用shell脚本或者跑Python脚本生成代码,after_build回调里直接用os.exec即可,简单到不用查文档。
- 不管用哪套系统,遇到“输出路径不对”“怎么生成PDB”“怎么执行自定义bash命令”这类问题,先花五分钟看看官方文档的“构建规则”和“自定义规则”板块,多数答案不在搜索引擎置顶帖里,而是在相关工具的官方路径里。
C++构建系统这盘棋,短期看还是CMake占主导,但XMake这种新势力带来的压力,某种程度上也在推动整个工具生态变得更关注开发体验。对我们这些写C++的人来说,多一个可体验的选项,总归不是坏事。