---
title: xmake v2.5.2 发布, 支持自动拉取交叉工具链和依赖包集成
tags: [xmake, lua, C/C++, toolchains, xrepo, packages, cross-toolchains]
date: 2021-02-27
author: Ruki
outline: deep
---
在 2.5.2 版本中，我们增加了一个重量级的新特性：`自动拉取远程交叉编译工具链`。

这是用来干什么的呢，做过交叉编译以及有 C/C++ 项目移植经验的同学应该知道，折腾各种交叉编译工具链，移植编译项目是非常麻烦的一件事，需要自己下载对应工具链，并且配置工具链和编译环境很容易出错导致编译失败。

现在，xmake 已经可以支持自动下载项目所需的工具链，然后使用对应工具链直接编译项目，用户不需要关心如何配置工具链，任何情况下只需要执行 `xmake` 命令即可完成编译。

![](/assets/img/posts/xmake/muslcc.gif)

甚至对于 C/C++ 依赖包的集成，也可以自动切换到对应工具链编译安装集成，一切完全自动化，完全不需要用户操心。

除了交叉编译工具链，我们也可以自动拉取工具链，比如特定版本的 llvm，llvm-mingw, zig 等各种工具链来参与编译 C/C++/Zig 项目的编译。

即使是 cmake 也还不支持工具链的自动拉取，顶多只能配合 vcpkg/conan 等第三方包管理对 C/C++ 依赖包进行集成，另外，即使对于 C/C++依赖包，xmake 也有自己原生内置的包管理工具，完全无任何依赖。






* [项目源码](https://github.com/xmake-io/xmake)
* [官方文档](https://xmake.io/zh/)
* [入门课程](https://xmake.io/zh/)









## 新特性介绍

### 自动拉取远程交叉编译工具链

从 2.5.2 版本开始，我们可以拉取指定的工具链来集成编译项目，我们也支持将依赖包切换到对应的远程工具链参与编译后集成进来。

相关例子代码见：[Toolchain/Packages Examples](https://github.com/xmake-io/xmake/tree/master/tests/projects/package)

相关的 issue [#1217](https://github.com/xmake-io/xmake/issues/1217)

当前，我们已经在 [xmake-repo](https://github.com/xmake-io/xmake-repo) 仓库收录了以下工具链包，可以让 xmake 远程拉取集成：

* llvm
* llvm-mingw
* gnu-rm
* muslcc
* zig

虽然现在支持的工具链包不多，当但是整体架构已经打通，后期我们只需要收录更多的工具链进来就行，比如：gcc, tinyc, vs-buildtools 等工具链。

由于 xmake 的包支持语义版本，因此如果项目依赖特定版本的 gcc/clang 编译器，就不要用户去折腾安装了，xmake 会自动检测当前系统的 gcc/clang 版本是否满足需求。

如果版本不满足，那么 xmake 就会走远程拉取，自动帮用户安装集成特定版本的 gcc/clang，然后再去编译项目。

#### 拉取指定版本的 llvm 工具链

我们使用 llvm-10 中的 clang 来编译项目。

```lua
add_requires("llvm 10.x", {alias = "llvm-10"})
target("test")
    set_kind("binary")
    add_files("src/*.c)
    set_toolchains("llvm@llvm-10")
```

其中，`llvm@llvm-10` 的前半部分为工具链名，也就是 `toolchain("llvm")`，后面的名字是需要被关联工具链包名，也就是 `package("llvm")`，不过如果设置了别名，那么会优先使用别名：`llvm-10`

另外，我们后续还会增加 gcc 工具链包到 xmake-repo，使得用户可以自由切换 gcc-10, gcc-11 等特定版本的 gcc 编译器，而无需用户去手动安装。

### 拉取交叉编译工具链

我们也可以拉取指定的交叉编译工具链来编译项目。

```lua
add_requires("muslcc")
target("test")
    set_kind("binary")
    add_files("src/*.c)
    set_toolchains("@muslcc")
```

muslcc 是 https://musl.cc 提供的一款交叉编译工具链，默认 xmake 会自动集成编译 `x86_64-linux-musl-` 目标平台。

当然，我们也可以通过 `xmake f -a arm64` 切换到 `aarch64-linux-musl-` 目标平台来进行交叉编译。

#### 拉取工具链并且集成对应工具链编译的依赖包

我们也可以使用指定的muslcc交叉编译工具链去编译和集成所有的依赖包。

```lua
add_requires("muslcc")
add_requires("zlib", "libogg", {system = false})

set_toolchains("@muslcc")

target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib", "libogg")
```

这个时候，工程里面配置的 zlib, libogg 等依赖包，也会切换使用 muslcc 工具链，自动下载编译然后集成进来。

我们也可以通过 `set_plat/set_arch` 固定平台，这样只需要一个 xmake 命令，就可以完成整个交叉编译环境的集成以及架构切换。

```lua
add_requires("muslcc")
add_requires("zlib", "libogg", {system = false})

set_plat("cross")
set_arch("arm64")
set_toolchains("@muslcc")

target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("zlib", "libogg")
```

完整例子见：[Examples (muslcc)](https://github.com/xmake-io/xmake/blob/master/tests/projects/package/toolchain_muslcc/xmake.lua)

#### 拉取集成 Zig 工具链

xmake 会先下载特定版本的 zig 工具链，然后使用此工具链编译 zig 项目，当然用户如果已经自行安装了 zig 工具链，xmake 也会自动检测对应版本是否满足，如果满足需求，那么会直接使用它，无需重复下载安装。

```lua
add_rules("mode.debug", "mode.release")
add_requires("zig 0.7.x")

target("test")
    set_kind("binary")
    add_files("src/*.zig")
    set_toolchains("@zig")
```

### 增加对 zig cc 编译器支持

`zig cc` 是 zig 内置的 c/c++ 编译器，可以完全独立进行 c/c++ 代码的编译和链接，完全不依赖 gcc/clang/msvc，非常给力。

因此，我们完全可以使用它来编译 c/c++ 项目，关键是 zig 的工具链还非常轻量，仅仅几十M 。

我们只需要切换到 zig 工具链即可完成编译：

```bash
$ xmake f --toolchain=zig
$ xmake
[ 25%]: compiling.release src/main.c
"zig cc" -c -arch x86_64 -fvisibility=hidden -O3 -DNDEBUG -o build/.objs/xmake_test/macosx/x86_64/release/src/main.c.o src/main.c
[ 50%]: linking.release test
"zig c++" -o build/macosx/x86_64/release/test build/.objs/xmake_test/macosx/x86_64/release/src/main.c.o -arch x86_64 -stdlib=libc++ -Wl,-x -lz
[100%]: build ok!
```

另外，`zig cc` 的另外一个强大之处在于，它还支持不同架构的交叉编译，太 happy 了。

通过 xmake，我们也只需再额外切换下架构到 arm64，即可实现对 arm64 的交叉编译，例如：

```bash
$ xmake f -a arm64 --toolchain=zig
$ xmake
[ 25%]: compiling.release src/main.c
"zig cc" -c -target aarch64-macos-gnu -arch arm64 -fvisibility=hidden -O3 -DNDEBUG -o build/.objs/xmake_test/macosx/arm64/release/src/main.c.o src/main.c
checking for flags (-MMD -MF) ... ok
checking for flags (-fdiagnostics-color=always) ... ok
[ 50%]: linking.release xmake_test
"zig c++" -o build/macosx/arm64/release/xmake_test build/.objs/xmake_test/macosx/arm64/release/src/main.c.o -target aarch64-macos-gnu -arch arm64 -stdlib=libc++ -Wl,-x -lz
[100%]: build ok!
```

即使你是在在 macOS，也可以用 `zig cc` 去交叉编译 windows/x64 目标程序，相当于替代了 mingw 干的事情。

```bash
$ xmake f -p windows -a x64 --toolchain=zig
$ xmake
```

### 自动导出所有 windows/dll 中的符号

cmake 中有这样一个功能：`WINDOWS_EXPORT_ALL_SYMBOLS`，安装 cmake 文档中的说法：

[https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html](https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html)

> Enable this boolean property to automatically create a module definition (.def) file with all global symbols found
> in the input .obj files for a SHARED library (or executable with ENABLE_EXPORTS) on Windows.
> The module definition file will be passed to the linker causing all symbols to be exported from the .dll. For global data symbols,
> __declspec(dllimport) must still be used when compiling against the code in the .dll. All other function symbols will be automatically exported and imported by callers.
> This simplifies porting projects to Windows by reducing the need for explicit dllexport markup, even in C++ classes.

大体意思就是：

> 启用此布尔属性，可以自动创建一个模块定义(.def)文件，其中包含在Windows上的共享库(或使用ENABLE_EXPORTS的可执行文件)的输入.obj文件中找到的所有全局符号。
> 模块定义文件将被传递给链接器，使所有符号从.dll中导出。对于全局数据符号，当对.dll中的代码进行编译时，仍然必须使用__declspec(dllimport)。
> 所有其它的函数符号将被调用者自动导出和导入。这就简化了将项目移植到 Windows 的过程，减少了对显式 dllexport 标记的需求，甚至在 C++ 类中也是如此。

现在，xmake 中也提供了类似的特性，可以快速全量导出 windows/dll 中的符号，来简化对第三方项目移植过程中，对符号导出的处理。另外，如果项目中的符号太多，也可以用此来简化代码中的显式导出需求。

我们只需在对应生成的 dll 目标上，配置 `utils.symbols.export_all` 规则即可。

```lua
target("foo")
    set_kind("shared")
    add_files("src/foo.c")
    add_rules("utils.symbols.export_all")

target("test")
    set_kind("binary")
    add_deps("foo")
    add_files("src/main.c")
```

xmake 会自动扫描所有的 obj 对象文件，然后生成 def 符号导出文件，传入 link.exe 实现快速全量导出。

### 转换 mingw/.dll.a 到 msvc/.lib

这个特性也是参考自 CMAKE_GNUtoMS 功能，可以把MinGW生成的动态库（xxx.dll & xxx.dll.a）转换成Visual Studio可以识别的格式（xxx.dll & xxx.lib），从而实现混合编译。

这个功能对Fortran & C++混合项目特别有帮助，因为VS不提供fortran编译器，只能用MinGW的gfortran来编译fortran部分，然后和VS的项目链接。
往往这样的项目同时有一些其他的库以vs格式提供，因此纯用MinGW编译也不行，只能使用cmake的这个功能来混合编译。

因此，xmake 也提供了一个辅助模块接口去支持它，使用方式如下：

```lua
import("utils.platform.gnu2mslib")

gnu2mslib("xxx.lib", "xxx.dll.a")
gnu2mslib("xxx.lib", "xxx.def")
gnu2mslib("xxx.lib", "xxx.dll.a", {dllname = "xxx.dll", arch = "x64"})
```

支持从 def 生成 xxx.lib ，也支持从 xxx.dll.a 自动导出 .def ，然后再生成 xxx.lib

具体细节见：[issue #1181](https://github.com/xmake-io/xmake/issues/1181)

### 实现批处理命令来简化自定义规则

为了简化用户自定义 rule 的配置，xmake 新提供了 `on_buildcmd_file`, `on_buildcmd_files` 等自定义脚本入口，
我们可以通过 batchcmds 对象，构造一个批处理命令行任务，xmake 在实际执行构建的时候，一次性执行这些命令。

这对于 `xmake project` 此类工程生成器插件非常有用，因为生成器生成的第三方工程文件并不支持 `on_build_files` 此类内置脚本的执行支持。

但是 `on_buildcmd_file` 构造的最终结果，就是一批原始的 cmd 命令行，可以直接给其他工程文件作为 custom commands 来执行。

另外，相比 `on_build_file`，它也简化对扩展文件的编译实现，更加的可读易配置，对用户也更加友好。

```lua
rule("foo")
    set_extensions(".xxx")
    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
        batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
    end)
```

除了 `batchcmds:vrunv`，我们还支持一些其他的批处理命令，例如：

```lua
batchcmds:show("hello %s", "xmake")
batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile}, {envs = {LD_LIBRARY_PATH="/xxx"}})
batchcmds:mkdir("/xxx") -- and cp, mv, rm, ln ..
batchcmds:compile(sourcefile_cx, objectfile, {configs = {includedirs = sourcefile_dir, languages = (sourcekind == "cxx" and "c++11")}})
batchcmds:link(objectfiles, targetfile, {configs = {linkdirs = ""}})
```

同时，我们在里面也简化对依赖执行的配置，下面是一个完整例子：

```lua
rule("lex")
    set_extensions(".l", ".ll")
    on_buildcmd_file(function (target, batchcmds, sourcefile_lex, opt)

        -- imports
        import("lib.detect.find_tool")

        -- get lex
        local lex = assert(find_tool("flex") or find_tool("lex"), "lex not found!")

        -- get c/c++ source file for lex
        local extension = path.extension(sourcefile_lex)
        local sourcefile_cx = path.join(target:autogendir(), "rules", "lex_yacc", path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))

        -- add objectfile
        local objectfile = target:objectfile(sourcefile_cx)
        table.insert(target:objectfiles(), objectfile)

        -- add commands
        batchcmds:show_progress(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)
        batchcmds:mkdir(path.directory(sourcefile_cx))
        batchcmds:vrunv(lex.program, {"-o", sourcefile_cx, sourcefile_lex})
        batchcmds:compile(sourcefile_cx, objectfile)

        -- add deps
        batchcmds:add_depfiles(sourcefile_lex)
        batchcmds:set_depmtime(os.mtime(objectfile))
        batchcmds:set_depcache(target:dependfile(objectfile))
    end)
```

我们从上面的配置可以看到，整体执行命令列表非常清晰，而如果我们用 `on_build_file` 来实现，可以对比下之前这个规则的配置，就能直观感受到新接口的配置方式确实简化了不少：

```lua
rule("lex")

    -- set extension
    set_extensions(".l", ".ll")

    -- load lex/flex
    before_load(function (target)
        import("core.project.config")
        import("lib.detect.find_tool")
        local lex = config.get("__lex")
        if not lex then
            lex = find_tool("flex") or find_tool("lex")
            if lex and lex.program then
                config.set("__lex", lex.program)
                cprint("checking for Lex ... ${color.success}%s", lex.program)
            else
                cprint("checking for Lex ... ${color.nothing}${text.nothing}")
                raise("lex/flex not found!")
            end
        end
    end)

    -- build lex file
    on_build_file(function (target, sourcefile_lex, opt)

        -- imports
        import("core.base.option")
        import("core.theme.theme")
        import("core.project.config")
        import("core.project.depend")
        import("core.tool.compiler")
        import("private.utils.progress")

        -- get lex
        local lex = assert(config.get("__lex"), "lex not found!")

        -- get extension: .l/.ll
        local extension = path.extension(sourcefile_lex)

        -- get c/c++ source file for lex
        local sourcefile_cx = path.join(target:autogendir(), "rules", "lex_yacc", path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))
        local sourcefile_dir = path.directory(sourcefile_cx)

        -- get object file
        local objectfile = target:objectfile(sourcefile_cx)

        -- load compiler
        local compinst = compiler.load((extension == ".ll" and "cxx" or "cc"), {target = target})

        -- get compile flags
        local compflags = compinst:compflags({target = target, sourcefile = sourcefile_cx})

        -- add objectfile
        table.insert(target:objectfiles(), objectfile)

        -- load dependent info
        local dependfile = target:dependfile(objectfile)
        local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {})

        -- need build this object?
        local depvalues = {compinst:program(), compflags}
        if not depend.is_changed(dependinfo, {lastmtime = os.mtime(objectfile), values = depvalues}) then
            return
        end

        -- trace progress info
        progress.show(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)

        -- ensure the source file directory
        if not os.isdir(sourcefile_dir) then
            os.mkdir(sourcefile_dir)
        end

        -- compile lex
        os.vrunv(lex, {"-o", sourcefile_cx, sourcefile_lex})

        -- trace
        if option.get("verbose") then
            print(compinst:compcmd(sourcefile_cx, objectfile, {compflags = compflags}))
        end

        -- compile c/c++ source file for lex
        dependinfo.files = {}
        assert(compinst:compile(sourcefile_cx, objectfile, {dependinfo = dependinfo, compflags = compflags}))

        -- update files and values to the dependent file
        dependinfo.values = depvalues
        table.insert(dependinfo.files, sourcefile_lex)
        depend.save(dependinfo, dependfile)
    end)
```

关于这个的详细说明和背景，见：[issue 1246](https://github.com/xmake-io/xmake/issues/1246)

### 依赖包配置改进

#### 使用 add_extsources 改进包名查找

关于远程依赖包定义这块，我们也新增了 `add_extsources` 和 `on_fetch` 两个配置接口，可以更好的配置 xmake 在安装 C/C++ 包的过程中，对系统库的查找过程。

至于具体背景，我们可以举个例子，比如我们在 [xmake-repo](https://github.com/xmake-io/xmake-repo) 仓库新增了一个 `package("libusb")` 的包。

那么用户就可以通过下面的方式，直接集成使用它：

```lua
add_requires("libusb")
target("test")
    set_kind("binary")
    add_files("src/*.c")
    add_packages("libusb")
```

如果用户系统上确实没有安装 libusb，那么 xmake 会自动下载 libusb 库源码，自动编译安装集成，没啥问题。

但如果用户通过 `apt install libusb-1.0` 安装了 libusb 库到系统，那么按理 xmake 应该会自动优先查找用户安装到系统环境的 libusb 包，直接使用，避免额外的下载编译安装。

但是问题来了，xmake 内部通过 `find_package("libusb")` 并没有找打它，这是为什么呢？因为通过 apt 安装的 libusb 包名是 `libusb-1.0`, 而不是 libusb。

我们只能通过 `pkg-config --cflags libusb-1.0` 才能找到它，但是 xmake 内部的默认 find_package 逻辑并不知道 `libusb-1.0` 的存在，所以找不到。

因此为了更好地适配不同系统环境下，系统库的查找，我们可以通过 `add_extsources("pkgconfig::libusb-1.0")` 去让 xmake 改进查找逻辑，例如：

```lua
package("libusb")
    add_extsources("pkgconfig::libusb-1.0")
    on_install(function (package)
        -- ...
    end)
```

另外，我们也可以通过这个方式，改进查找 homebrew/pacman 等其他包管理器安装的包，例如：`add_extsources("pacman::libusb-1.0")`。

#### 使用 on_fetch 完全定制系统库查找

如果不同系统下安装的系统库，仅仅只是包名不同，那么使用 `add_extsources` 改进系统库查找已经足够，简单方便。

但是如果有些安装到系统的包，位置更加复杂，想要找到它们，也许需要一些额外的脚本才能实现，例如：windows 下注册表的访问去查找包等等，这个时候，我们就可以通过 `on_fetch` 完全定制化查找系统库逻辑。

还是以 libusb 为例，我们不用 `add_extsources`，可以使用下面的方式，实现相同的效果，当然，我们可以在里面做更多的事情。

```
package("libusb")
    on_fetch("linux", function(package, opt)
        if opt.system then
            return find_package("pkgconfig::libusb-1.0")
        end
    end)
```

### manifest 文件支持

在新版本中，我们还新增了对 windows `.manifest` 文件的支持，只需要通过 `add_files` 添加进来即可。

```lua
add_rules("mode.debug", "mode.release")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_files("src/*.manifest")
```

### xrepo 命令改进

关于 xrepo 命令，我们也稍微改进了下，现在可以通过下面的命令，批量卸载删除已经安装的包，支持模式匹配：

```bash
$ xrepo remove --all
$ xrepo remove --all zlib pcr*
```

### 包的依赖导出支持

我们也改进了 `add_packages`，使其也支持 `{public = true}` 来导出包配置给父 target。


```lua
add_rules("mode.debug", "mode.release")
add_requires("pcre2")

target("test")
    set_kind("shared")
    add_packages("pcre2", {public = true})
    add_files("src/test.cpp")

target("demo")
    add_deps("test")
    set_kind("binary")
    add_files("src/main.cpp")  -- 我们可以在这里使用被 test 目标导出 pcre2 库
```

至于具体导出哪些配置呢？

```lua
-- 默认私有，但是 links/linkdirs 还是会自动导出
add_packages("pcre2")

-- 全部导出。包括 includedirs, defines
add_packages("pcre2", {public = true})
```

## 更新内容

### 新特性

* [#955](https://github.com/xmake-io/xmake/issues/955#issuecomment-766481512): 支持 `zig cc` 和 `zig c++` 作为 c/c++ 编译器
* [#955](https://github.com/xmake-io/xmake/issues/955#issuecomment-768193083): 支持使用 zig 进行交叉编译
* [#1177](https://github.com/xmake-io/xmake/issues/1177): 改进终端和 color codes 探测
* [#1216](https://github.com/xmake-io/xmake/issues/1216): 传递自定义 includes 脚本给 xrepo
* 添加 linuxos 内置模块获取 linux 系统信息
* [#1217](https://github.com/xmake-io/xmake/issues/1217): 支持当编译项目时自动拉取工具链
* [#1123](https://github.com/xmake-io/xmake/issues/1123): 添加 `rule("utils.symbols.export_all")` 自动导出所有 windows/dll 中的符号
* [#1181](https://github.com/xmake-io/xmake/issues/1181): 添加 `utils.platform.gnu2mslib(mslib, gnulib)` 模块接口去转换 mingw/xxx.dll.a 到 msvc xxx.lib
* [#1246](https://github.com/xmake-io/xmake/issues/1246): 改进规则支持新的批处理命令去简化自定义规则实现
* [#1239](https://github.com/xmake-io/xmake/issues/1239): 添加 `add_extsources` 去改进外部包的查找
* [#1241](https://github.com/xmake-io/xmake/issues/1241): 支持为 windows 程序添加 .manifest 文件参与链接
* 支持使用 `xrepo remove --all` 命令去移除所有的包，并且支持模式匹配
* [#1254](https://github.com/xmake-io/xmake/issues/1254): 支持导出包配置给父 target，实现包配置的依赖继承

### 改进

* [#1226](https://github.com/xmake-io/xmake/issues/1226): 添加缺失的 Qt 头文件搜索路径
* [#1183](https://github.com/xmake-io/xmake/issues/1183): 改进 C++ 语言标准，以便支持 Qt6
* [#1237](https://github.com/xmake-io/xmake/issues/1237): 为 vsxmake 插件添加 qt.ui 文件
* 改进 vs/vsxmake 插件去支持预编译头文件和智能提示
* [#1090](https://github.com/xmake-io/xmake/issues/1090): 简化自定义规则
* [#1065](https://github.com/xmake-io/xmake/issues/1065): 改进 protobuf 规则，支持 compile_commands 生成器
* [#1249](https://github.com/xmake-io/xmake/issues/1249): 改进 vs/vsxmake 生成器去支持启动工程设置
* [#605](https://github.com/xmake-io/xmake/issues/605): 改进 add_deps 和 add_packages 直接的导出 links 顺序
* 移除废弃的 `add_defines_h_if_ok` and `add_defines_h` 接口

### Bugs 修复

* [#1219](https://github.com/xmake-io/xmake/issues/1219): 修复版本检测和更新
* [#1235](https://github.com/xmake-io/xmake/issues/1235): 修复 includes 搜索路径中带有空格编译不过问题
