乐趣区

关于c++:xmake-v259-发布改进-C20-模块并支持-Nim-Keil-MDK-和-Unity-Build

xmake 是一个基于 Lua 的轻量级跨平台构建工具,应用 xmake.lua 保护我的项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对老手十分敌对,短时间内就能疾速入门,可能让用户把更多的精力集中在理论的我的项目开发上。

这个版本,咱们减少了大量重量级的新个性,例如:Nim 语言我的项目的构建反对,Keil MDK,Circle 和 Wasi 工具链反对。

另外,咱们对 C++20 Modules 进行了大改良,不仅反对最新 gcc-11, clang 和 msvc 编译器,而且还得模块间依赖做了主动剖析,实现最大水平的并行化编译反对。

最初,还有一个比拟有用的个性就是 Unity Build 反对,通过它咱们能够对 C++ 代码的编译速度做到很大水平的晋升。

  • 我的项目源码
  • 官网文档
  • 入门课程

新个性介绍

Nimlang 我的项目构建

最近,咱们新增了对 Nimlang 我的项目的构建反对,相干 issues 见:#1756

创立空工程

咱们能够应用 xmake create 命令创立空工程。

xmake create -l nim -t console test
xmake create -l nim -t static test
xmake create -l nim -t shared test

控制台程序

add_rules("mode.debug", "mode.release")

target("test")
    set_kind("binary")
    add_files("src/main.nim")
$ xmake -v
[33%]: linking.release test
/usr/local/bin/nim c --opt:speed --nimcache:build/.gens/test/macosx/x86_64/release/nimcache -o:b
uild/macosx/x86_64/release/test src/main.nim
[100%]: build ok!

动态库程序

add_rules("mode.debug", "mode.release")

target("foo")
    set_kind("static")
    add_files("src/foo.nim")

target("test")
    set_kind("binary")
    add_deps("foo")
    add_files("src/main.nim")
$ xmake -v
[33%]: linking.release libfoo.a
/usr/local/bin/nim c --opt:speed --nimcache:build/.gens/foo/macosx/x86_64/release/nimcache --app
:staticlib --noMain --passC:-DNimMain=NimMain_B6D5BD02 --passC:-DNimMainInner=NimMainInner_B6D5B
D02 --passC:-DNimMainModule=NimMainModule_B6D5BD02 --passC:-DPreMain=PreMain_B6D5BD02 --passC:-D
PreMainInner=PreMainInner_B6D5BD02 -o:build/macosx/x86_64/release/libfoo.a src/foo.nim
[66%]: linking.release test
/usr/local/bin/nim c --opt:speed --nimcache:build/.gens/test/macosx/x86_64/release/nimcache --pa
ssL:-Lbuild/macosx/x86_64/release --passL:-lfoo -o:build/macosx/x86_64/release/test src/main.nim
[100%]: build ok!

动静库程序

add_rules("mode.debug", "mode.release")

target("foo")
    set_kind("shared")
    add_files("src/foo.nim")

target("test")
    set_kind("binary")
    add_deps("foo")
    add_files("src/main.nim")
$ xmake -rv
[33%]: linking.release libfoo.dylib
/usr/local/bin/nim c --opt:speed --nimcache:build/.gens/foo/macosx/x86_64/release/nimcache --app
:lib --noMain -o:build/macosx/x86_64/release/libfoo.dylib src/foo.nim
[66%]: linking.release test
/usr/local/bin/nim c --opt:speed --nimcache:build/.gens/test/macosx/x86_64/release/nimcache --pa
ssL:-Lbuild/macosx/x86_64/release --passL:-lfoo -o:build/macosx/x86_64/release/test src/main.nim
[100%]: build ok!

C 代码混合编译

add_rules("mode.debug", "mode.release")

target("foo")
    set_kind("static")
    add_files("src/*.c")

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

Nimble 依赖包集成

残缺例子见:Nimble Package Example

add_rules("mode.debug", "mode.release")

add_requires("nimble::zip >0.3")

target("test")
    set_kind("binary")
    add_files("src/main.nim")
    add_packages("nimble::zip")

main.nim

import zip/zlib

echo zlibVersion()

Native 依赖包集成

残缺例子见:Native Package Example

add_rules("mode.debug", "mode.release")

add_requires("zlib")

target("test")
    set_kind("binary")
    add_files("src/main.nim")
    add_packages("zlib")

main.nim

proc zlibVersion(): cstring {.cdecl, importc}

echo zlibVersion()

Unity Build 减速

咱们晓得,C++ 代码编译速度通常很慢,因为每个代码文件都须要解析引入的头文件。

而通过 Unity Build,咱们通过将多个 cpp 文件组合成一个来减速我的项目的编译,其次要益处是缩小了解析和编译蕴含在多个源文件中的头文件内容的反复工作,头文件的内容通常占预处理后源文件中的大部分代码。

Unity 构建还通过缩小编译链创立和解决的指标文件的数量来加重因为领有大量小源文件而导致的开销,并容许跨造成对立构建工作的文件进行过程间剖析和优化(相似于成果链接时优化)。

它能够极大晋升 C/C++ 代码的编译速度,通常会有 30% 的速度晋升,不过依据我的项目的复杂程度不同,其带来的效益还是要依据本身我的项目状况而定。

xmake 在 v2.5.9 版本中,也曾经反对了这种构建模式。相干 issues 见 #1019。

如何启用?

咱们提供了两个内置规定,别离解决对 C 和 C++ 代码的 Unity Build。

add_rules("c.unity_build")
add_rules("c++.unity_build")

Batch 模式

默认状况下,只有设置上述规定,就会启用 Batch 模式的 Unity Build,也就是 xmake 主动依据我的项目代码文件,主动组织合并。

target("test")
    set_kind("binary")
    add_includedirs("src")
    add_rules("c++.unity_build", {batchsize = 2})
    add_files("src/*.c", "src/*.cpp")

咱们能够额定通过设置 {batchsize = 2} 参数到规定,来指定每个合并 Batch 的大小数量,这里也就是每两个 C++ 文件主动合并编译。

编译成果大略如下:

$ xmake -r
[11%]: ccache compiling.release build/.gens/test/unity_build/unity_642A245F.cpp
[11%]: ccache compiling.release build/.gens/test/unity_build/unity_bar.cpp
[11%]: ccache compiling.release build/.gens/test/unity_build/unity_73161A20.cpp
[11%]: ccache compiling.release build/.gens/test/unity_build/unity_F905F036.cpp
[11%]: ccache compiling.release build/.gens/test/unity_build/unity_foo.cpp
[11%]: ccache compiling.release build/.gens/test/unity_build/main.c
[77%]: linking.release test
[100%]: build ok

因为咱们仅仅启用了 C++ 的 Unity Build,所以 C 代码还是失常挨个编译。另外在 Unity Build 模式下,咱们还是能够做到尽可能的并行编译减速,互不抵触。

如果没有设置 batchsize 参数,那么默认会吧所有文件合并到一个文件中进行编译。

Group 模式

如果下面的 Batch 模式主动合并成果不现实,咱们也能够应用自定义分组,来手动配置哪些文件合并到一起参加编译,这使得用户更加地灵便可控。

target("test")
    set_kind("binary")
    add_rules("c++.unity_build", {batchsize = 0}) -- disable batch mode
    add_files("src/*.c", "src/*.cpp")
    add_files("src/foo/*.c", {unity_group = "foo"})
    add_files("src/bar/*.c", {unity_group = "bar"})

咱们应用 {unity_group = "foo"} 来指定每个分组的名字,以及蕴含了哪些文件,每个分组的文件都会独自被合并到一个代码文件中去。

另外,batchsize = 0 也强行禁用了 Batch 模式,也就是说,没有设置 unity_group 分组的代码文件,咱们还是会独自编译它们,也不会主动开启主动合并。

Batch 和 Group 混合模式

咱们只有把下面的 batchsize = 0 改成非 0 值,就能够让分组模式下,残余的代码文件持续开启 Batch 模式主动合并编译。

target("test")
    set_kind("binary")
    add_includedirs("src")
    add_rules("c++.unity_build", {batchsize = 2})
    add_files("src/*.c", "src/*.cpp")
    add_files("src/foo/*.c", {unity_group = "foo"})
    add_files("src/bar/*.c", {unity_group = "bar"})

疏忽指定文件

如果是 Batch 模式下,因为是主动合并操作,所以默认会对所有文件执行合并,但如果有些代码文件咱们不想让它参加合并,那么咱们也能够通过 {unity_ignored = true} 去疏忽它们。

target("test")
    set_kind("binary")
    add_includedirs("src")
    add_rules("c++.unity_build", {batchsize = 2})
    add_files("src/*.c", "src/*.cpp")
    add_files("src/test/*.c", {unity_ignored = true}) -- ignore these files

Unique ID

只管 Unity Build 带啦的收益不错,然而咱们还是会遇到一些意外的状况,比方咱们的两个代码文件外面,全局命名空间下,都存在雷同名字的全局变量和函数。

那么,合并编译就会带来编译抵触问题,编译器通常会报全局变量重定义谬误。

为了解决这个问题,咱们须要用户代码上做一些批改,而后配合构建工具来解决。

比方,咱们的 foo.cpp 和 bar.cpp 都有全局变量 i。

foo.cpp

namespace {int i = 42;}

int foo()
{return i;}

bar.cpp

namespace {int i = 42;}

int bar()
{return i;}

那么,咱们合并编译就会抵触,咱们能够引入一个 Unique ID 来隔离全局的匿名空间。

foo.cpp

namespace MY_UNITY_ID {int i = 42;}

int foo()
{return MY_UNITY_ID::i;}

bar.cpp

namespace MY_UNITY_ID {int i = 42;}

int bar()
{return MY_UNITY_ID::i;}

接下来,咱们还须要保障代码合并后,MY_UNITY_ID 在 foo 和 bar 中的定义齐全不同,能够按文件名算一个惟一 ID 值进去,互不抵触,也就是实现上面的合并成果:

#define MY_UNITY_ID <hash(foo.cpp)>
#include "foo.c"
#undef MY_UNITY_ID
#define MY_UNITY_ID <hash(bar.cpp)>
#include "bar.c"
#undef MY_UNITY_ID

这看上去仿佛很麻烦,然而用户不须要关怀这些,xmake 会在合并时候主动解决它们,用户只须要指定这个 Unique ID 的名字就行了,例如上面这样:

target("test")
    set_kind("binary")
    add_includedirs("src")
    add_rules("c++.unity_build", {batchsize = 2, uniqueid = "MY_UNITY_ID"})
    add_files("src/*.c", "src/*.cpp")

解决全局变量,还有全局的重名宏定义,函数什么的,都能够采纳这种形式来防止抵触。

C++20 Modules

xmake 采纳 .mpp 作为默认的模块扩展名,然而也同时反对 .ixx, .cppm, .mxx 等扩展名。

晚期,xmake 试验性反对过 C++ Modules TS,然而那个时候,gcc 还不能很好的反对,并且模块间的依赖也不反对。

最近,咱们对 xmake 做了大量改良,曾经残缺反对 gcc-11/clang/msvc 的 C++20 Modules 构建反对,并且可能主动剖析模块间的依赖关系,实现最大化并行编译。

同时,对新版本的 clang/msvc 也做了更好地解决。

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

更多例子见:C++ Modules

Lua5.4 运行时反对

上个版本,咱们减少了对 Lua5.3 运行时反对,而在这个版本中,咱们进一步降级 Lua 运行时到 5.4,相比 5.3,运行性能和内存利用率上都有很大的晋升。

不过,目前 xmake 的默认运行时还是 luajit,预计 2.6.1 版本(也就是下个版本),会正式切到 Lua5.4 作为默认的运行时。

只管切换了 Lua 运行时,然而对于用户端,齐全是无感知的,并且齐全兼容现有工程配置,因为 xmake 本来就对裸露的 api 提供了一层封装,
对于 lua 版本之间存在兼容性问题的接口,例如 setfenv, ffi 等都暗藏在外部,本来就没有裸露给用户应用。

Keil MDK 工具链反对

咱们在这个版本中,还新增了 Keil/MDK 嵌入式编译工具链的反对,相干例子工程:Example

xmake 会主动探测 Keil/MDK 装置的编译器,相干 issues #1753。

应用 armcc 编译

$ xmake f -p cross -a cortex-m3 --toolchain=armcc -c
$ xmake

应用 armclang 编译

$ xmake f -p cross -a cortex-m3 --toolchain=armclang -c
$ xmake

控制台程序

target("hello")
    add_deps("foo")
    add_rules("mdk.console")
    add_files("src/*.c", "src/*.s")
    add_defines("__EVAL", "__MICROLIB")
    add_includedirs("src/lib/cmsis")

动态库程序

add_rules("mode.debug", "mode.release")

target("foo")
    add_rules("mdk.static")
    add_files("src/foo/*.c")

Wasi 工具链反对

之前咱们反对了 wasm 平台的 emcc 工具链来构建 wasm 程序,而这里,咱们新加了另外一个启用了 WASI 的 Wasm 工具链来替换 emcc。

$ xmake f -p wasm --toolchain=wasi
$ xmake

Circle 工具链反对

咱们还新增了 circle 编译器的反对,这是个新的 C++20 编译器,额定附带了一些乏味的编译期元编程个性,有趣味的同学能够到官网查看:https://www.circle-lang.org/

$ xmake f --toolchain=circle
$ xmake

gcc-8/9/10/11 特定版本反对

如果用户额定装置了 gcc-11, gcc-10 等特定版本的 gcc 工具链,在本地的 gcc 程序命名可能是 /usr/bin/gcc-11

一种方法是通过 xmake f --cc=gcc-11 --cxx=gcc-11 --ld=g++-11 挨个指定配置来切换,但十分繁琐。

所以,xmake 也提供了更加快捷的切换形式:

$ xmake f --toolchain=gcc-11 -c
$ xmake

只须要指定 gcc-11 对应的版本名,就能够疾速切换整个 gcc 工具链。

C++17/20 编译器个性检测

xmake 提供了 check_features 辅助接口来检测编译器个性。

includes("check_features.lua")

target("test")
    set_kind("binary")
    add_files("*.c")
    add_configfiles("config.h.in")
    configvar_check_features("HAS_CONSTEXPR", "cxx_constexpr")
    configvar_check_features("HAS_CONSEXPR_AND_STATIC_ASSERT", {"cxx_constexpr", "c_static_assert"}, {languages = "c++11"})

config.h.in

${define HAS_CONSTEXPR}
${define HAS_CONSEXPR_AND_STATIC_ASSERT}

config.h

/* #undef HAS_CONSTEXPR */
#define HAS_CONSEXPR_AND_STATIC_ASSERT 1

而在 2.5.9 版本中,咱们新增了 c++17 个性检测:

个性名
cxx_aggregate_bases
cxx_aligned_new
cxx_capture_star_this
cxx_constexpr
cxx_deduction_guides
cxx_enumerator_attributes
cxx_fold_expressions
cxx_guaranteed_copy_elision
cxx_hex_float
cxx_if_constexpr
cxx_inheriting_constructors
cxx_inline_variables
cxx_namespace_attributes
cxx_noexcept_function_type
cxx_nontype_template_args
cxx_nontype_template_parameter_auto
cxx_range_based_for
cxx_static_assert
cxx_structured_bindings
cxx_template_template_args
cxx_variadic_using

还新增了 c++20 个性检测:

个性名
cxx_aggregate_paren_init
cxx_char8_t
cxx_concepts
cxx_conditional_explicit
cxx_consteval
cxx_constexpr
cxx_constexpr_dynamic_alloc
cxx_constexpr_in_decltype
cxx_constinit
cxx_deduction_guides
cxx_designated_initializers
cxx_generic_lambdas
cxx_impl_coroutine
cxx_impl_destroying_delete
cxx_impl_three_way_comparison
cxx_init_captures
cxx_modules
cxx_nontype_template_args
cxx_using_enum

Xrepo 包虚拟环境治理

进入虚拟环境

xmake 自带的 xrepo 包管理工具,当初曾经能够很好的反对包虚拟机环境治理,相似 nixos 的 nixpkgs。

咱们能够通过在当前目录下,增加 xmake.lua 文件,定制化一些包配置,而后进入特定的包虚拟环境。

add_requires("zlib 1.2.11")
add_requires("python 3.x", "luajit")
$ xrepo env shell
> python --version
> luajit --version

咱们也能够在 xmake.lua 配置加载对应的工具链环境,比方加载 vs 的编译环境。

set_toolchains("msvc")

治理虚拟环境

咱们能够应用上面的命令,把指定的虚拟环境配置全局注册到零碎中,不便疾速切换。

$ xrepo env --add /tmp/base.lua

这个时候,咱们就保留了一个名叫 base 的全局虚拟环境,咱们能够通过 list 命令去查看它。

$ xrepo env --list
/Users/ruki/.xmake/envs:
  - base
envs(1) found!

咱们也能够删除它。

$ xrepo env --remove base

切换全局虚拟环境

如果咱们注册了多个虚拟环境,咱们也能够疾速切换它们。

$ xrepo env -b base shell
> python --version

或者间接加载指定虚拟环境运行特定命令

$ xrepo env -b base python --version

xrepo env -b/--bind 就是绑定指定的虚拟环境,更多详情见:#1762

Header Only 指标类型

对于 target,咱们新增了 headeronly 指标类型,这个类型的目标程序,咱们不会理论编译它们,因为它没有源文件须要被编译。

然而它蕴含了头文件列表,这通常用于 headeronly 库我的项目的装置,IDE 工程的文件列表生成,以及装置阶段的 cmake/pkgconfig 导入文件的生成。

例如:

add_rules("mode.release", "mode.debug")

target("foo")
    set_kind("headeronly")
    add_headerfiles("src/foo.h")
    add_rules("utils.install.cmake_importfiles")
    add_rules("utils.install.pkgconfig_importfiles")

更多详情见:#1747

从 CMake 中查找包

当初 cmake 曾经是事实上的规范,所以 CMake 提供的 find_package 曾经能够查找大量的库和模块,咱们齐全复用 cmake 的这部分生态来裁减 xmake 对包的集成。

咱们能够通过 find_package("cmake::xxx") 去借助 cmake 来找一些包,xmake 会主动生成一个 cmake 脚本来调用 cmake 的 find_package 去查找一些包,获取里面包信息。

例如:

$ xmake l find_package cmake::ZLIB
{
  links = {"z"},
  includedirs = {
    "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.
15.sdk/usr/include"
  },
  linkdirs = {
    "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.
15.sdk/usr/lib"
  }
}
$ xmake l find_package cmake::LibXml2
{
  links = {"xml2"},
  includedirs = {"/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/libxml2"},
  linkdirs = {"/usr/lib"}
}

指定版本

find_package("cmake::OpenCV", {required_version = "4.1.1"})

指定组件

find_package("cmake::Boost", {components = {"regex", "system"}})

预设开关

find_package("cmake::Boost", {components = {"regex", "system"}, presets = {Boost_USE_STATIC_LIB = true}})
set(Boost_USE_STATIC_LIB ON) -- will be used in FindBoost.cmake
find_package(Boost REQUIRED COMPONENTS regex system)

设置环境变量

find_package("cmake::OpenCV", {envs = {CMAKE_PREFIX_PATH = "xxx"}})

指定自定义 FindFoo.cmake 模块脚本目录

mydir/cmake_modules/FindFoo.cmake

find_package("cmake::Foo", {moduledirs = "mydir/cmake_modules"})

包依赖集成

package("xxx")
    on_fetch(function (package, opt)
         return package:find_package("cmake::xxx", opt)
    end)
package_end()

add_requires("xxx")

包依赖集成(可选组件)

package("boost")
    add_configs("regex",   { description = "Enable regex.", default = false, type = "boolean"})
    on_fetch(function (package, opt)
         opt.components = {}
         if package:config("regex") then
             table.insert(opt.components, "regex")
         end
         return package:find_package("cmake::Boost", opt)
    end)
package_end()

add_requires("boost", {configs = {regex = true}})

相干 issues: #1632

增加自定义命令到 CMakelists.txt

咱们进一步改良了 cmake 生成器,当初能够将 rule 外面自定义的脚本序列化成命令列表,一起生成到 CMakelists.txt

不过目前只能反对 batchcmds 系列脚本的序列化。

rule("foo")
    after_buildcmd(function (target, batchcmds, opt)
        batchcmds:show("hello xmake!")
        batchcmds:cp("xmake.lua", "/tmp/")
        -- batchcmds:execv("echo", {"hello", "world!"})
        -- batchcmds:runv("echo", {"hello", "world!"})
    end)

target("test")
    set_kind("binary")
    add_rules("foo")
    add_files("src/*.c")

它将会生成相似如下的 CMakelists.txt

# ...
add_custom_command(TARGET test
    POST_BUILD
    COMMAND echo hello xmake!
    VERBATIM
)
add_custom_command(TARGET test
    POST_BUILD
    COMMAND cp xmake.lua /tmp/
    VERBATIM
)
target_sources(test PRIVATE
    src/main.c
)

不过 cmake 的 ADD_CUSTOM_COMMAND PRE_BUILD 实际效果在不同生成器上,差别比拟大,无奈满足咱们的需要,因而咱们做了很多解决来反对它。

相干 issues: #1735

改良对 NixOS 的装置反对

咱们还改良了 get.sh 装置脚本,来更好地反对 nixOS。

更新内容

新个性

  • #1736: 反对 wasi-sdk 工具链
  • 反对 Lua 5.4 运行时
  • 增加 gcc-8, gcc-9, gcc-10, gcc-11 工具链
  • #1623: 反对 find_package 从 cmake 查找包
  • #1747: 增加 set_kind("headeronly") 更好的解决 headeronly 库的装置
  • #1019: 反对 Unity build
  • #1438: 减少 xmake l cli.amalgamate 命令反对代码合并
  • #1765: 反对 nim 语言
  • #1762: 为 xrepo env 治理和切换指定的环境配置
  • #1767: 反对 Circle 编译器
  • #1753: 反对 Keil/MDK 的 armcc/armclang 工具链
  • #1774: 增加 table.contains api
  • #1735: 增加自定义命令到 cmake 生成器
  • #1781: 改良 get.sh 装置脚本反对 nixos

改良

  • #1528: 检测 c++17/20 个性
  • #1729: 改良 C++20 modules 对 clang/gcc/msvc 的反对,反对模块间依赖编译和并行优化
  • #1779: 改良 ml.exe/x86,移除内置的 -Gd 选项
退出移动版