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/C++ 包中集成特定的子库,一般用于一些比较大的包中的库组件集成。

因为这种包里面供给了许多的子库,但不是每个子库用户都需求,悉数链接反而有可能会出问题。

虽然,之前的版别也能够支撑子库挑选的特性,例如:

add_requires("sfml~foo", {configs = {graphics = true, window = true}})
add_requires("sfml~bar", {configs = {network = true}})
target("foo")
    set_kind("binary")
    add_packages("sfml~foo")
target("bar")
    set_kind("binary")
    add_packages("sfml~bar")

这是经过每个包的自定义装备来完成的,但这种方法会存在一些问题:

  1. sfml~foosfml~bar 会作为两个独立的包,重复装置,占用双倍的磁盘空间
  2. 也会重复编译一些共用代码,影响装置功率
  3. 假如一个目标一起依靠了 sfml~foosfml~bar,会存在链接冲突

假如是关于 boost 这种超大包的集成,重复编译和磁盘占用的影响会十分大,假如在子库组合十分多的状况下,甚至会导致超越 N 倍的磁盘占用。

为了处理这个问题,Xmake 新增了包组件形式,它供给了以下一些好处:

  1. 只是一次编译装置,恣意多个组件快速集成,极大提高装置功率,削减磁盘占用
  2. 组件笼统化,跨编译器和渠道,用户不需求关怀怎么装备每个子库之间链接顺序依靠
  3. 运用愈加便利

更多布景概况见:#2636

运用包组件

关于用户,运用包组件是十分便利的,因为用户是不需求保护包的,只要运用的包,它装备了相关的组件集,咱们就能够快速集成和运用它,例如:

add_requires("sfml")
target("foo")
    set_kind("binary")
    add_packages("sfml", {components = "graphics"})
target("bar")
    set_kind("binary")
    add_packages("sfml", {components = "network"})

检查包组件

那么,怎么知道指定的包供给了哪些组件呢?咱们能够经过执行下面的指令检查:

$ xrepo info sfml
The package info of project:
    require(sfml):
      -> description: Simple and Fast Multimedia Library
      -> version: 2.5.1
      ...
      -> components:
         -> system:
         -> graphics: system, window
         -> window: system
         -> audio: system
         -> network: system

包组件装备

假如你是包的保护者,想要将一个包增加组件支撑,那么需求经过下面两个接口来完成包组件的装备:

  • add_components: 增加包组件列表
  • on_component: 装备每个包组件
包组件的链接装备

大多数状况下,包组件只需求装备它自己的一些子链接信息,例如:

package("sfml")
    add_components("graphics")
    add_components("audio", "network", "window")
    add_components("system")
    on_component("graphics", function (package, component)
        local e = package:config("shared") and "" or "-s"
        component:add("links", "sfml-graphics" .. e)
        if package:is_plat("windows", "mingw") and not package:config("shared") then
            component:add("links", "freetype")
            component:add("syslinks", "opengl32", "gdi32", "user32", "advapi32")
        end
    end)
    on_component("window", function (package, component)
        local e = package:config("shared") and "" or "-s"
        component:add("links", "sfml-window" .. e)
        if package:is_plat("windows", "mingw") and not package:config("shared") then
            component:add("syslinks", "opengl32", "gdi32", "user32", "advapi32")
        end
    end)
    ...

上面是一个不完好的包装备,我只是摘取一部分跟包组件相关的装备。

一个关于包组件的装备和运用的完好比如见:components example

装备组件的编译信息

咱们不只能够装备每个组件的链接信息,还有 includedirs, defines 等等编译信息,咱们也能够对每个组件独自装备。

package("sfml")
    on_component("graphics", function (package, component)
        package:add("defines", "TEST")
    end)
装备组件依靠
package("sfml")
    add_components("graphics")
    add_components("audio", "network", "window")
    add_components("system")
    on_component("graphics", function (package, component)
          component:add("deps", "window", "system")
    end)

上面的装备,告诉包,咱们的 graphics 组件还会额定依靠 windowsystem 两个组件。

因而,在用户端,咱们对 graphics 的组件运用,能够从

    add_packages("sfml", {components = {"graphics", "window", "system"})

简化为:

    add_packages("sfml", {components = "graphics")

因为,只要咱们敞开了 graphics 组件,它也会主动启用依靠的 window 和 system 组件,并且主动保证链接顺序正确。

别的,咱们也能够经过 add_components("graphics", {deps = {"window", "system"}}) 来装备组件依靠联系。

从体系库中查找组件

咱们知道,在包装备中,装备 add_extsources 能够改善包在体系中的查找,比如从 apt/pacman 等体系包办理器中找库。

当然,咱们也能够让每个组件也能经过 extsources 装备,去优先从体系库中找到它们。

例如,sfml 包,它在 homebrew 中其实也是组件化的,咱们彻底能够让包从体系库中,找到对应的每个组件,而不需求每次源码装置它们。

$ ls -l /usr/local/opt/sfml/lib/pkgconfig
-r--r--r--  1 ruki  admin  317 10 19 17:52 sfml-all.pc
-r--r--r--  1 ruki  admin  534 10 19 17:52 sfml-audio.pc
-r--r--r--  1 ruki  admin  609 10 19 17:52 sfml-graphics.pc
-r--r--r--  1 ruki  admin  327 10 19 17:52 sfml-network.pc
-r--r--r--  1 ruki  admin  302 10 19 17:52 sfml-system.pc
-r--r--r--  1 ruki  admin  562 10 19 17:52 sfml-window.pc

咱们只需求,对每个组件装备它的 extsources:

    if is_plat("macosx") then
        add_extsources("brew::sfml/sfml-all")
    end
    on_component("graphics", function (package, component)
        -- ...
        component:add("extsources", "brew::sfml/sfml-graphics")
    end)
默许的大局组件装备

除了经过指定组件名的方法,装备特定组件,假如咱们没有指定组件名,默许就是大局装备一切组件。

package("sfml")
    on_component(function (package, component)
        -- configure all components
    end)

当然,咱们也能够经过下面的方法,指定装备 graphics 组件,剩下的组件经过默许的大局装备接口进行装备:

package("sfml")
    add_components("graphics")
    add_components("audio", "network", "window")
    add_components("system")
    on_component("graphics", function (package, component)
        -- configure graphics
    end)
    on_component(function (package, component)
        -- component audio, network, window, system
    end)

C++ 模块构建改善

增量构建支撑

原本认为 Xmake 对 C++ 模块现已支撑的比较完善了,后来才发现,它的增量编译还无法正常作业。

因而,这个版别 Xmake 对 C++ 模块的增量编译也做了很好的支撑,虽然支撑进程仍是花了许多精力的。

我分析了下,各家的编译器对生成带模块的 include 依靠信息格局(*.d),差异仍是十分大的。

gcc 的格局最杂乱,不过我仍是将它支撑上了。

build/.objs/dependence/linux/x86_64/release/src/foo.mpp.o: src/foo.mpp\
build/.objs/dependence/linux/x86_64/release/src/foo.mpp.o  gcm.cache/foo.gcm: bar.c++m cat.c++m\
foo.c++m: gcm.cache/foo.gcm\
.PHONY: foo.c++m\
gcm.cache/foo.gcm:|  build/.objs/dependence/linux/x86_64/release/src/foo.mpp.o\
CXX_IMPORTS += bar.c++m cat.c++m\

clang 的格局兼容性最好,没有做任何特殊改动就支撑了。

build//hello.pcm:   /usr/lib/llvm-15/lib/clang/15.0.2/include/module.modulemap   src/hello.mpp\

msvc 的格局扩展性比较好,解析和支撑起来比较便利:

{
    "Version": "1.2",
    "Data": {
        "Source": "c:\users\ruki\desktop\user_headerunit\src\main.cpp",
        "ProvidedModule": "",
        "Includes": [],
        "ImportedModules": [
            {
                "Name": "hello",
                "BMI": "c:\users\ruki\desktop\user_headerunit\src\hello.ifc"
            }
        ],
        "ImportedHeaderUnits": [
            {
                "Header": "c:\users\ruki\desktop\user_headerunit\src\header.hpp",
                "BMI": "c:\users\ruki\desktop\user_headerunit\src\header.hpp.ifc"
            }
        ]
    }
}

循环依靠检测支撑

因为模块之间是存在依靠联系的,因而假如有几个模块之间存在循环依靠引用,那么是无法编译经过的。

可是之前的版别中,Xmake 无法检测到这种状况,遇到循环依靠,编译就会卡死,没有任何提示信息,这对用户十分不友爱。

而新版别中,咱们对这种状况做了改善,增加了模块的循环依靠检测,编译时候会出现以下错误提示,便利用户定位问题:

$ xmake
[  0%]: generating.cxx.module.deps Foo.mpp
[  0%]: generating.cxx.module.deps Foo2.mpp
[  0%]: generating.cxx.module.deps Foo3.mpp
[  0%]: generating.cxx.module.deps main.cpp
error: circular modules dependency(Foo2, Foo, Foo3, Foo2) detected!
  -> module(Foo2) in Foo2.mpp
  -> module(Foo) in Foo.mpp
  -> module(Foo3) in Foo3.mpp
  -> module(Foo2) in Foo2.mpp

愈加 LSP 友爱的语法格局

咱们默许约好的域装备语法,虽然十分简练,可是对主动格局化缩进和 IDE 不是很友爱,假如你格局化装备,缩进就彻底错位了。

target("foo")
    set_kind("binary")
    add_files("src/*.cpp")

别的,假如两个 target 之间装备了一些大局的装备,那么它不能主动完毕当时 target 作用域,用户需求显式调用 target_end()

target("foo")
    set_kind("binary")
    add_files("src/*.cpp")
target_end()
add_defines("ROOT")
target("bar")
    set_kind("binary")
    add_files("src/*.cpp")

虽然,上面咱们说到,能够运用 do end 形式来处理主动缩进问题,可是需求 target_end() 的问题仍是存在。

target("foo") do
    set_kind("binary")
    add_files("src/*.cpp")
end
target_end()
add_defines("ROOT")
target("bar") do
    set_kind("binary")
    add_files("src/*.cpp")
end

因而,在新版别中,咱们供给了一种更好的可选域装备语法,来处理主动缩进,target 域阻隔问题,例如:

target("foo", function ()
    set_kind("binary")
    add_files("src/*.cpp")
end)
add_defines("ROOT")
target("bar", function ()
    set_kind("binary")
    add_files("src/*.cpp")
end)

foo 和 bar 两个域是彻底阻隔的,咱们即便在它们中间装备其他设置,也不会影响它们,别的,它还对 LSP 十分友爱,即便一键格局化,也不会导致缩进紊乱。

注:这只是只是一只可选的扩展语法,现有的装备语法仍是彻底支撑的,用户能够依据自己的需求喜爱,来挑选合适的装备语法。

为特定编译器增加 flags

运用 add_cflags, add_cxxflags 等接口装备的值,一般都是跟编译器相关的,虽然 Xmake 也供给了主动检测和映射机制,
即便设置了当时编译器不支撑的 flags,Xmake 也能够主动疏忽它,可是仍是会有正告提示。

新版别中,咱们改善了一切 flags 增加接口,能够只是对特定编译器指定 flags,来防止额定的正告,例如:

add_cxxflags("clang::-stdlib=libc++")
add_cxxflags("gcc::-stdlib=libc++")

或许:

add_cxxflags("-stdlib=libc++", {tools = "clang"})
add_cxxflags("-stdlib=libc++", {tools = "gcc"})

注:不只是是编译flags,对 add_ldflags 等链接 flags,也是相同生效的。

renderdoc 调试器支撑

感谢 @SirLynix 奉献了这个很棒的特性,它能够让 Xmake 直接加载 renderdoc 去调试一些图形渲染程序。

运用十分简单,咱们先保证装置了 renderdoc,然后装备调试器为 renderdoc,加载调试运转:

$ xmake f --debugger=renderdoc
$ xmake run -d

详细运用作用如下:

新增 C++ 反常接口装备

Xmake 新增了一个 set_exceptions 笼统化装备接口,咱们能够经过这个装备,装备启用和禁用 C++/Objc 的反常。

一般,假如咱们经过 add_cxxflags 接口去装备它们,需求依据不同的渠道,编译器别离处理它们,十分繁琐。

例如:

on_config(function (target)
    if (target:has_tool("cxx", "cl")) then
        target:add("cxflags", "/EHsc", {force = true})
        target:add("defines", "_HAS_EXCEPTIONS=1", {force = true})
    elseif(target:has_tool("cxx", "clang") or target:has_tool("cxx", "clang-cl")) then
        target:add("cxflags", "-fexceptions", {force = true})
        target:add("cxflags", "-fcxx-exceptions", {force = true})
    end
end)

而经过这个接口,咱们就能够笼统化成编译器无关的方法去装备它们。

敞开 C++ 反常:

set_exceptions("cxx")

禁用 C++ 反常:

set_exceptions("no-cxx")

咱们也能够一起装备敞开 objc 反常。

set_exceptions("cxx", "objc")

或许禁用它们。

set_exceptions("no-cxx", "no-objc")

Xmake 会在内部主动依据不同的编译器,去适配对应的 flags。

支撑 ispc 编译规矩

Xmake 新增了 ipsc 编译器内置规矩支撑,十分感谢 @star-hengxing 的奉献,详细运用方法如下:

target("test")
    set_kind("binary")
    add_rules("utils.ispc", {header_extension = "_ispc.h"})
    set_values("ispc.flags", "--target=host")
    add_files("src/*.ispc")
    add_files("src/*.cpp")

支撑 msvc 的 armasm 编译器

之前的版别,Xmake 增加了 Windows ARM 的开始支撑,可是对 asm 编译还没有很好的支撑,因而这个版别,咱们继续完善 Windows ARM 的支撑。

对 msvc 的 armasm.exearmasm64.exe 都支撑上了。

别的,咱们也改善了包对 Windows ARM 渠道的交叉编译支撑。

新增 gnu-rm 构建规矩

Xmake 也新增了一个运用 gnu-rm 东西链去构建嵌入式项目的规矩和比如工程,十分感谢 @JacobPeng 的奉献。

add_rules("mode.debug", "mode.release")
add_requires("gnu-rm")
set_toolchains("@gnu-rm")
set_plat("cross")
set_arch("armv7")
target("foo")
    add_rules("gnu-rm.static")
    add_files("src/foo/*.c")
target("hello")
    add_deps("foo")
    add_rules("gnu-rm.binary")
    add_files("src/*.c", "src/*.S")
    add_files("src/*.ld")
    add_includedirs("src/lib/cmsis")

完好工程见:Embed GNU-RM Example

新增 OpenBSD 体系支撑

之前的版别,Xmake 只是支撑 FreeBSD 体系,而 OpenBSD 跟 FreeBSD 仍是有不少差异的,导致 Xmake 无法在它上面正常编译装置。

而新版别现已彻底支撑在 OpenBSD 上运转 Xmake 了。

更新内容

新特性

  • 一种新的可选域装备语法,对 LSP 友爱,并且支撑域阻隔。
  • #2944: 为嵌入式工程增加 gnu-rm.binarygnu-rm.static 规矩和测验工程
  • #2636: 支撑包组件
  • 支撑 msvc 的 armasm/armasm64
  • #3023: 改善 xmake run -d,增加 renderdoc 调试器支撑
  • #3022: 为特定编译器增加 flags
  • #3025: 新增 C++ 反常接口装备
  • #3017: 支撑 ispc 编译器规矩

改善

  • #2925: 改善 doxygen 插件
  • #2948: 支撑 OpenBSD
  • 增加 xmake g --insecure-ssl=y 装备选项去禁用 ssl 证书检测
  • #2971: 使 vs/vsxmake 工程生成的成果每次保持一致
  • #3000: 改善 C++ 模块构建支撑,完成增量编译支撑
  • #3016: 改善 clang/msvc 去更好地支撑 std 模块

Bugs 修正

  • #2949: 修正 vs 分组
  • #2952: 修正 armlink 处理长指令失利问题
  • #2954: 修正 c++ module partitions 途径无效问题
  • #3033: 探测循环模块依靠

tboox.org/cn/2022/11/…