xmake v2.5.2 发布, 支持自动拉取交叉工具链和依赖包集成

xmake 是一个基于 Lua 的轻量级跨平台构建工具,使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对新手很是友好,短期内就能快速入门,可以让用户把更多的精力集中在实际的项目开发上。html

在 2.5.2 版本中,咱们增长了一个重量级的新特性:自动拉取远程交叉编译工具链linux

这是用来干什么的呢,作过交叉编译以及有 C/C++ 项目移植经验的同窗应该知道,折腾各类交叉编译工具链,移植编译项目是很是麻烦的一件事,须要本身下载对应工具链,而且配置工具链和编译环境很容易出错致使编译失败。c++

如今,xmake 已经能够支持自动下载项目所需的工具链,而后使用对应工具链直接编译项目,用户不须要关心如何配置工具链,任何状况下只须要执行 xmake 命令便可完成编译。git

甚至对于 C/C++ 依赖包的集成,也能够自动切换到对应工具链编译安装集成,一切彻底自动化,彻底不须要用户操心。github

除了交叉编译工具链,咱们也能够自动拉取工具链,好比特定版本的 llvm,llvm-mingw, zig 等各类工具链来参与编译 C/C++/Zig 项目的编译。macos

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

新特性介绍

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

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

相关例子代码见:Toolchain/Packages Examplesmarkdown

相关的 issue #1217架构

当前,咱们已经在 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 来编译项目。

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 编译器,而无需用户去手动安装。

拉取交叉编译工具链

咱们也能够拉取指定的交叉编译工具链来编译项目。

add_requires("muslcc")
target("test")
    set_kind("binary")
    add_files("src/*.c) set_toolchains("@muslcc") 复制代码

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

固然,咱们也能够经过 xmake f -a arm64 切换到 aarch64-linux-musl- 目标平台来进行交叉编译。

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

咱们也可使用指定的muslcc交叉编译工具链去编译和集成全部的依赖包。

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 命令,就能够完成整个交叉编译环境的集成以及架构切换。

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)

拉取集成 Zig 工具链

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

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 工具链便可完成编译:

$ 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 的交叉编译,例如:

$ 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 干的事情。

$ xmake f -p windows -a x64 --toolchain=zig
$ xmake
复制代码

自动导出全部 windows/dll 中的符号

cmake 中有这样一个功能:WINDOWS_EXPORT_ALL_SYMBOLS,安装 cmake 文档中的说法:

cmake.org/cmake/help/…

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 规则便可。

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 也提供了一个辅助模块接口去支持它,使用方式以下:

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

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

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

这对于 xmake project 此类工程生成器插件很是有用,由于生成器生成的第三方工程文件并不支持 on_build_files 此类内置脚本的执行支持。

可是 on_buildcmd_file 构造的最终结果,就是一批原始的 cmd 命令行,能够直接给其余工程文件做为 custom commands 来执行。

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

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

除了 batchcmds:vrunv,咱们还支持一些其余的批处理命令,例如:

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 = ""}})
复制代码

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

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 来实现,能够对比下以前这个规则的配置,就能直观感觉到新接口的配置方式确实简化了很多:

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

依赖包配置改进

使用 add_extsources 改进包名查找

关于远程依赖包定义这块,咱们也新增了 add_extsourceson_fetch 两个配置接口,能够更好的配置 xmake 在安装 C/C++ 包的过程当中,对系统库的查找过程。

至于具体背景,咱们能够举个例子,好比咱们在 xmake-repo 仓库新增了一个 package("libusb") 的包。

那么用户就能够经过下面的方式,直接集成使用它:

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 改进查找逻辑,例如:

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 添加进来便可。

add_rules("mode.debug", "mode.release")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_files("src/*.manifest")
复制代码

xrepo 命令改进

关于 xrepo 命令,咱们也稍微改进了下,如今能够经过下面的命令,批量卸载删除已经安装的包,支持模式匹配:

$ xrepo remove --all
$ xrepo remove --all zlib pcr*
复制代码

包的依赖导出支持

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

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 库
复制代码

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

-- 默认私有,可是 links/linkdirs 仍是会自动导出
add_packages("pcre2")

-- 所有导出。包括 includedirs, defines
add_packages("pcre2", {public = true})
复制代码

更新内容

新特性

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

改进

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

Bugs 修复

  • #1219: 修复版本检测和更新
  • #1235: 修复 includes 搜索路径中带有空格编译不过问题
相关文章
相关标签/搜索