V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
waruqi
V2EX  ›  程序员

Xmake v2.7.1 发布,更好的 C++ Modules 支持

  •  
  •   waruqi ·
    waruqi · 2022-08-27 10:24:31 +08:00 · 1975 次点击
    这是一个创建于 849 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Xmake 是一个基于 Lua 的轻量级跨平台构建工具。

    它非常的轻量,没有任何依赖,因为它内置了 Lua 运行时。

    它使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt ,配置语法更加简洁直观,对新手非常友好,短时间内就能快速入门,能够让用户把更多的精力集中在实际的项目开发上。

    我们能够使用它像 Make/Ninja 那样可以直接编译项目,也可以像 CMake/Meson 那样生成工程文件,另外它还有内置的包管理系统来帮助用户解决 C/C++ 依赖库的集成使用问题。

    目前,Xmake 主要用于 C/C++ 项目的构建,但是同时也支持其他 native 语言的构建,可以实现跟 C/C++ 进行混合编译,同时编译速度也是非常的快,可以跟 Ninja 持平。

    Xmake = Build backend + Project Generator + Package Manager + [Remote|Distributed] Build + Cache
    

    尽管不是很准确,但我们还是可以把 Xmake 按下面的方式来理解:

    Xmake ~= Make/Ninja + CMake/Meson + Vcpkg/Conan + distcc + ccache/sccache
    

    新特性介绍

    这个版本我们对 C++20 Modules 的实现进行了重构和改进,改进了模块文件的依赖图解析,新增了对 STL 和 User HeaderUnits 的支持,同时让 CMakelists/compile_commands 生成器也支持了 C++ Modules 。

    另外,我们新增了一个 xmake watch 插件,可以实时监控当前工程文件更新,自动触发增量构建,或者运行一些自定义的命令。

    C++ Modules 改进

    Xmake 很早就已经支持 C++ Modules 的构建支持,并且能够自动分析模块间的依赖关系,实现最大化的并行编译。 另外,Xmake 采用 .mpp 作为默认的模块扩展名,但是也同时支持 .ixx, .cppm, .mxx 等扩展名。

    例如:

    set_languages("c++20")
    target("class")
        set_kind("binary")
        add_files("src/*.cpp", "src/*.mpp")
    

    更多例子见:C++ Modules

    但是之前的实现还存在很多不足之处:

    1. 不支持 HeaderUnits ,因此也无法使用 stl 等模块
    2. 自己扫描源码实现模块依赖图解析,不支持编译器提供的依赖扫描,因此不完全可靠
    3. 不支持 CMakelists 生成
    4. 不支持 compile_commands.json 生成

    而在新版中,我们对 C++20 模块的实现进行了重构和升级,上面提到的几点,我们都做了支持,新增了对 Headerunits 的支持,因此我们可以在模块中引入 STL 和 用户头文件模块。

    同时,由于 msvc 和 gcc 高版本 都已经内置对模块依赖图的扫描分析,Xmake 会优先借助编译器实现模块依赖图分析,如果编译器不支持( clang ),那么 Xmake 也会退化到自己的源码扫描实现上去。

    相关的补丁见:#2641,非常感谢 @Arthapz 的贡献。

    下面是一个使用了 STL HeaderUnits 的模块例子,例如:

    stl_headerunit$ xmake
    [  0%]: generating.cxx.module.deps src/main.cpp
    [  0%]: generating.cxx.module.deps src/hello.mpp
    [ 20%]: generating.cxx.headerunit.bmi iostream
    [ 60%]: generating.cxx.module.bmi hello
    [ 70%]: cache compiling.release src/main.cpp
    [ 80%]: linking.release stl_headerunit
    [100%]: build ok!
    

    对于首次编译,我们会扫描模块代码之间的依赖关系,然后预编译 iostream 等 stl 库作为 headerunit 。

    之后的重新编译,都会直接复用它们,实现编译加速。

    注:通常我们至少需要添加一个 .mpp 文件,才能开启 C++20 modules 编译,如果只有 cpp 文件,默认是不会开启模块编译的。

    但是,如果我们仅仅只是想在 cpp 文件中使用模块的 Headerunits 特性,比如引入一些 STL Headerunits 在 cpp 中使用, 那么我们也可以通过设置 set_policy("build.c++.modules", true) 来强行开启 C++ Modules 编译,例如:

    add_rules("mode.debug", "mode.release")
    
    target("test")
        set_kind("binary")
        add_files("src/*.cpp")
        set_languages("c++20")
        set_policy("build.c++.modules", true)
    

    工程文件监视和自动构建

    这个版本中,我们新增了 xmake watch 插件命令,可以自动监视项目文件更新,然后触发自动构建,或者运行一些自定义命令。

    这通常用于个人开发时候,实现快速的实时增量编译,而不需要每次手动执行编译命令,提高开发效率。

    项目更新后自动构建

    默认行为就是监视整个项目根目录,任何文件改动都会触发项目的增量编译。

    $ xmake watch
    watching /private/tmp/test/src/** ..
    watching /private/tmp/test/* ..
    /private/tmp/test/src/main.cpp modified
    [ 25%]: ccache compiling.release src/main.cpp
    [ 50%]: linking.release test
    [100%]: build ok!
    

    监视指定目录

    我们也可以监视指定的代码目录,缩小监视范围,提升监视性能。

    $ xmake watch -d src
    $ xmake watch -d "src;tests/*"
    

    上面的命令,会去递归监视所有子目录,如果想要仅仅监视当前目录下的文件,不进行递归监视,可以使用下面的命令。

    $ xmake watch -p src
    $ xmake watch -p "src;tests/*"
    

    监视并运行指定命令

    如果想在自动构建后,还想自动运行构建的程序,我们可以使用自定义的命令集。

    $ xmake watch -c "xmake; xmake run"
    

    上面的命令列表是作为字符串传递,这对于复杂命令参数,需要转义比较繁琐不够灵活,那么我们可以使用下面的方式进行任意命令的设置。

    $ xmake watch -- echo hello xmake!
    $ xmake watch -- xmake run --help
    

    监视并运行目标程序

    尽管我们可以通过自定义命令来实现目标程序的自动运行,但是我们也提供了更加方便的参数来实现这个行为。

    $ xmake watch -r
    $ xmake watch --run
    [100%]: build ok!
    hello world!
    

    监视并运行 lua 脚本

    我们还可以监视文件更新后,运行指定的 lua 脚本,实现更加灵活复杂的命令定制。

    $ xmake watch -s /tmp/test.lua
    

    我们还可以再脚本中获取所有更新的文件路径列表和事件。

    function main(events)
        -- TODO handle events
    end
    

    Mac Catalyst 支持

    MAc Catalyst 是苹果后来新推的一项让 iPad App 带入 Mac 的方案,通过 Mac Catalyst 构建的 Mac App 与您的 iPad App 共享代码,而且您可以单独为 Mac 添加更多功能。

    新版本中,我们新增了 Mac Catalyst 目标的构建支持,在 macOS 平台上,我们只需要添加 --appledev=catalyst 配置选项,就可以支持编译现有的 iOS 代码,并让它在 macOS 上运行起来,而无需做任何改动。

    $ xmake f --appledev=catalyst
    $ xmake
    

    我们可以在 iosapp_with_framework 这个测试项目中体验 Mac Catalyst 程序的编译运行。

    $ xmake
    [ 36%]: processing.xcode.release src/framework/Info.plist
    [ 40%]: cache compiling.release src/framework/test.m
    [ 44%]: linking.release test
    [ 48%]: generating.xcode.release test.framework
    [ 56%]: compiling.xcode.release src/app/Assets.xcassets
    [ 56%]: processing.xcode.release src/app/Info.plist
    [ 60%]: cache compiling.release src/app/ViewController.m
    [ 60%]: cache compiling.release src/app/SceneDelegate.m
    [ 60%]: cache compiling.release src/app/main.m
    [ 60%]: cache compiling.release src/app/AppDelegate.m
    [ 60%]: compiling.xcode.release src/app/Base.lproj/LaunchScreen.storyboard
    [ 60%]: compiling.xcode.release src/app/Base.lproj/Main.storyboard
    [ 88%]: linking.release demo
    [ 92%]: generating.xcode.release demo.app
    [100%]: build ok!
    $ xmake run
    2022-08-26 15:11:03.581 demo[86248:9087199] add(1, 2): 3
    2022-08-26 15:11:03.581 demo[86248:9087199] hello xmake!
    

    改进远程编译

    拉取远程构建文件

    对于远程编译,我们新增加了一个拉取远程文件的命令,通常可用于远程编译完成后,下载远程的目标生成文件,库文件到本地。

    $ xmake service --pull 'build/**' outputdir
    

    我们可以通过 --pull 'build/**' 模式匹配需要下载的文件,可以是构建文件,也可以是其他文件。

    注:文件是按项目隔离的,只能指定下载当前项目下的文件,并不会让用户下载服务器上其他目录下的文件,避免一些安全隐患。

    实时回显输出

    先前的版本在使用远程编译的时候,客户端是无法实时输出服务端的编译信息的,由于缓存的存在,本地看到的编译进度信息都是一块一块刷新出来,体验不是很好。

    因此我们加上了行缓冲刷新支持,提高了输出回显的实时性,使得用户在远程编译时,更接近本地编译的体验。

    改进分布式编译调度算法

    我们对 xmake 的分布式编译的服务器节点调度也做了进一步改进,加上了 cpu 负载和内存资源的权重,而不仅仅通过 cpu core 数量来分配任务。

    因此,如果某些节点负载过高,我们会优先将编译任务调度到相当比较空闲的节点上去,充分利用所有编译资源。

    更灵活的 cmake 包查找

    指定链接

    对于 cmake 包,我们新增了 link_libraries 配置选项,让用户在查找使用 cmake 包的时候,可以自定义配置包依赖的链接库,甚至对 target 链接的支持。

    add_requires("cmake::xxx", {configs = {link_libraries = {"abc::lib1", "abc::lib2"}}})
    

    xmake 在查找 cmake 包的时候,会自动追加下面的配置,改进对 links 库的提取。

    target_link_libraries(test PRIVATE ABC::lib1 ABC::lib2)
    

    指定搜索模式

    另外,我们增加的搜索模式配置:

    add_requires("cmake::xxx", {configs = {search_mode = "config"}})
    add_requires("cmake::xxx", {configs = {search_mode = "module"}})
    add_requires("cmake::xxx") -- both
    

    比如指定 config 搜索模式,告诉 cmake 从 XXXConfig.cmake 中查找包。

    xmake 在查找 cmake 包的时候,内部会自动追加下面的配置。

    find_package(ABC CONFIG REQUIRED)
    

    armcc/armclang/rc 增量编译支持

    在新版本中,我们对 keil 的 armcc/armclang 编译器也进行头文件依赖分析,来支持增量编译。

    另外,msvc 的 rc.exe 资源编译器本身是无法提供头文件依赖分析的,但是 cl.exe 的预处理器却是可以处理资源文件的。 因此我们可以通过 cl.exe /E test.rc 去预处理资源文件,从中提取依赖信息,来实现资源文件的增量编译支持。

    目前测试下来,效果还不错,同时我们也对内部 ICON/BITMAP 的资源引用依赖也做了支持。

    其他问题修复

    我们对构建缓存也做了很多修复,它将比之前的版本更加的稳定。另外我们也精简了 CMakelists 的生成。

    更多细节改进见下面的更新日志:

    更新内容

    新特性

    • #2555: 添加 fwatcher 模块和 xmake watch 插件命令
    • 添加 xmake service --pull 'build/**' outputdir 命令去拉取远程构建服务器上的文件
    • #2641: 改进 C++20 模块, 支持 headerunits 和 project 生成
    • #2679: 支持 Mac Catalyst 构建

    改进

    • #2576: 改进从 cmake 中查找包,提供更过灵活的可选配置
    • #2577: 改进 add_headerfiles(),增加 {install = false} 支持
    • #2603: 为 ccache 默认禁用 -fdirectives-only
    • #2580: 设置 stdout 到 line 缓冲输出
    • #2571: 改进分布式编译的调度算法,增加 cpu/memory 状态权重
    • #2410: 改进 cmakelists 生成
    • #2690: 改机传递 toolchains 到包
    • #2686: 改进 armcc/armclang 支持增量编译
    • #2562: 改进 rc.exe 对引用文件依赖的解析和增量编译支持
    • 改进默认的并行构建任务数

    Bugs 修复

    • #2614: 为 msvc 修复构建 submodules2 测试工程
    • #2620: 修复构建缓存导致的增量编译问题
    • #2177: 修复 python.library 在 macOS 上段错误崩溃
    • #2708: 修复 mode.coverage 规则的链接错误
    • 修复 ios/macOS framework 和 application 的 rpath 加载路径
    8 条回复    2022-08-27 17:28:46 +08:00
    L4Linux
        1
    L4Linux  
       2022-08-27 14:06:10 +08:00 via Android
    初次知道这个项目,有几个疑问:

    对 target 设置 flag 之前,会检查编译器支持这个 flag 吗?可以通过变量设置 flag 的内容吗?后面这点是考虑到不同编译器实现相同功能的 flag 不一样。还有,有没有可选的 flag ,检测到编译器不支持就不设置,而不是报错?

    多个优化等级可以同时设置吗? O2 和 Os 经常一起用。

    给一段代码,检查编译器是否能编译出正确的二进制文件,这种功能有吗?

    可以检查某头文件存不存在某个符号之类的吗?
    L4Linux
        2
    L4Linux  
       2022-08-27 14:08:29 +08:00 via Android
    还有,如何区分编译时的 flag 和链接时的 flag 呢?
    waruqi
        3
    waruqi  
    OP
       2022-08-27 15:01:09 +08:00 via Android
    > 对 target 设置 flag 之前,会检查编译器支持这个 flag 吗?可以通过变量设置 flag 的内容吗?后面这点是考虑到不同编译器实现相同功能的 flag 不一样。还有,有没有可选的 flag ,检测到编译器不支持就不设置,而不是报错?

    目前默认行为就是自动检测,不支持的 flags 自动忽略,部分 flags 还能自动映射到对应编译器

    > 多个优化等级可以同时设置吗? O2 和 Os 经常一起用。

    原本这个就不能同时支持,我没听说 gcc 还是 clang 能同时设置两个的,即使你同时设置了 -O2 -Os ,最后生效的也是最右边的 -Os

    另外,你非要同时设置,自己 add_cxflags("-O2", "-Os") 加上就行了

    > 给一段代码,检查编译器是否能编译出正确的二进制文件,这种功能有吗?

    支持,代码片段检测,接口检测,运行检测什么的都有

    > 可以检查某头文件存不存在某个符号之类的吗?

    当然有
    waruqi
        4
    waruqi  
    OP
       2022-08-27 15:02:47 +08:00 via Android
    > 还有,如何区分编译时的 flag 和链接时的 flag 呢?

    编译 add_cxflags add_cflags
    链接 add_ldflags
    动态链接 add_shflags

    具体你可以看文档 什么都有
    Chipmunker
        5
    Chipmunker  
       2022-08-27 15:28:38 +08:00
    之前看到过这个工具,想在项目中使用,但是不知道怎么实现在系统中找安装好的库。找了一下也没发现类似的示例代码(很久之前了,不知道现在什么情况)。
    waruqi
        6
    waruqi  
    OP
       2022-08-27 16:08:57 +08:00 via Android
    @Chipmunker 只需要 add_requires("zlib")

    优先查找系统,找不到自动装,如果只想查找不安装 add_requires("zlib", {system = false})
    waruqi
        7
    waruqi  
    OP
       2022-08-27 16:09:24 +08:00 via Android
    system = true ,刚敲错了
    L4Linux
        8
    L4Linux  
       2022-08-27 17:28:46 +08:00
    @waruqi #3 O2 Os 这个是我弄混了。应该是 Os enable O2 不增加体积的 flags 。

    看起来挺值得尝试的,天下苦 CMake 久矣。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1528 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:02 · PVG 01:02 · LAX 09:02 · JFK 12:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.