This is a mirror page, please see the original page:

https://xmake.io/#/zh-cn/guide/build_policies

xmake 有很多的默认行为,比如:自动检测和映射flags、跨target并行构建等,虽然提供了一定的智能化处理,但重口难调,不一定满足所有的用户的使用习惯和需求。

因此,从v2.3.4开始,xmake 提供默认构建策略的修改设置,开放给用户一定程度上的可配置性。

它主要通过 set_policy 接口来配置。

我们通常可以用它来配置修改 target,package 以及工程整体的一些行为策略。

使用方式如下:

set_policy("check.auto_ignore_flags", false)

只需要在项目根域设置这个配置,就可以禁用flags的自动检测和忽略机制,另外set_policy也可以针对某个特定的target局部生效。

target("test")
    set_policy("check.auto_ignore_flags", false)

!> 另外,如果设置的策略名是无效的,xmake也会有警告提示。

如果要获取当前xmake支持的所有策略配置列表和描述,可以执行下面的命令:

$ xmake l core.project.policy.policies
{
  "check.auto_map_flags" = {
    type = "boolean",
    description = "Enable map gcc flags to the current compiler and linker automatically.",
    default = true
  },
  "build.across_targets_in_parallel" = {
    type = "boolean",
    description = "Enable compile the source files for each target in parallel.",
    default = true
  },
  "check.auto_ignore_flags" = {
    type = "boolean",
    description = "Enable check and ignore unsupported flags automatically.",
    default = true
  }
}

我们也可以通过命令行的方式去设置修改内部的策略:

$ xmake f --policies=package.fetch_only

默认设置策略名,就是启用状态,当然我们也可以指定设置其他值,禁用它。

$ xmake f --policies=package.precompiled:n

或者同时配置多个策略值,用逗号分割。

$ xmake f --policies=package.precompiled:n,package.install_only

check.auto_ignore_flags

xmake默认会对所有add_cxflags, add_ldflags接口设置的原始flags进行自动检测,如果检测当前编译器和链接器不支持它们,就会自动忽略。

这通常是很有用的,像一些可选的编译flags,即使不支持也能正常编译,但是强行设置上去,其他用户在编译的时候,有可能会因为编译器的支持力度不同,出现一定程度的编译失败。

但,由于自动检测并不保证100%可靠,有时候会有一定程度的误判,所以某些用户并不喜欢这个设定(尤其是针对交叉编译工具链,更容易出现失败)。

目前,v2.3.4版本如果检测失败,会有警告提示避免用户莫名躺坑,例如:

warning: add_ldflags("-static") is ignored, please pass `{force = true}` or call `set_policy("check.auto_ignore_flags", false)` if you want to set it.

根据提示,我们可以自己分析判断,是否需要强制设置这个flags,一种就是通过:

add_ldflags("-static", {force = true})

来显示的强制设置上它,跳过自动检测,这对于偶尔的flags失败,是很有效快捷的处理方式,但是对于交叉编译时候,一堆的flags设置检测不过的情况下,每个都设置force太过于繁琐。

这个时候,我们就可以通过set_policy来对某个target或者整个project直接禁用默认的自动检测行为:

set_policy("check.auto_ignore_flags", false)
target("test")
    add_ldflags("-static")

然后我们就可以随意设置各种原始flags,xmake不会去自动检测和忽略他们了。

check.auto_map_flags

这是xmake的另外一个对flags的智能分析处理,通常像add_links, add_defines这种xmake内置的api去设置的配置,是具有跨平台特性的,不同编译器平台会自动处理成对应的原始flags。

但是,有些情况,用户还是需要自己通过add_cxflags, add_ldflags设置原始的编译链接flags,这些flags并不能很好的跨编译器

就拿-O0的编译优化flags来说,虽然有set_optimize来实现跨编译器配置,但如果用户直接设置add_cxflags("-O0")呢?gcc/clang下可以正常处理,但是msvc下就不支持了

也许我们能通过if is_plat() then来分平台处理,但很繁琐,因此xmake内置了flags的自动映射功能。

基于gcc flags的普及性,xmake采用gcc的flags命名规范,对其根据不同的编译实现自动映射,例如:

add_cxflags("-O0")

这一行设置,在gcc/clang下还是-O0,但如果当前是msvc编译器,那边会自动映射为msvc对应-Od编译选项来禁用优化。

整个过程,用户是完全无感知的,直接执行xmake就可以跨编译器完成编译。

!> 当然,目前的自动映射实现还不是很成熟,没有100%覆盖所有gcc的flags,所以还是有不少flags是没去映射的。

也有部分用户并不喜欢这种自动映射行为,那么我们可以通过下面的设置完全禁用这个默认的行为:

set_policy("check.auto_map_flags", false)

build.across_targets_in_parallel

这个策略也是默认开启的,主要用于跨target间执行并行构建,v2.3.3之前的版本,并行构建只能针对单个target内部的所有源文件,
跨target的编译,必须要要等先前的target完全link成功,才能执行下一个target的编译,这在一定程度上会影响编译速度。

然而每个target的源文件是可以完全并行化处理的,最终在一起执行link过程,v2.3.3之后的版本通过这个优化,构建速度提升了30%。

当然,如果有些特殊的target里面的构建源文件要依赖先前的target(尤其是一些自定义rules的情况,虽然很少遇到),我们也可以通过下面的设置禁用这个优化行为:

set_policy("build.across_targets_in_parallel", false)

build.fence

由于配置 set_policy("build.across_targets_in_parallel", false) 存在局限性,它会限制父 target 和它的所有依赖的子 target 之间的并行度,影响的范围有点大。

而我们做 codegen 时候,有时候仅仅只是想对其中某个依赖的 target 限制并行度,作为 codegen 程序,提前让它完成编译。

这个时候,build.across_targets_in_parallel 就无法精细控制了,编译速度也无法达到最优。

因此,我们新增了 build.fence 策略,它可以仅仅只针对特定的子 target 限制并行编译链接。

相关的背景细节,可以看下:#5003

例如:

target("autogen")
    set_default(false)
    set_kind("binary")
    set_plat(os.host())
    set_arch(os.arch())
    add_files("src/autogen.cpp")
    set_languages("c++11")
    set_policy("build.fence", true)

target("test")
    set_kind("binary")
    add_deps("autogen")
    add_rules("autogen")
    add_files("src/main.cpp")
    add_files("src/*.in")

其中 autogen 目标程序需要在 test 程序的源码被编译前,就要完成编译链接,因为 test 目标需要运行 autogen 程序,去动态生成一些源码参与编译。

而针对 autogen 配置 set_policy("build.fence", true) 就可以实现这个目的。

build.merge_archive

如果设置了这个策略,那么使用 add_deps() 依赖的目标库不再作为链接存在,而是直接把它们合并到父目标库中去。

例如:

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

target("add")
    set_kind("static")
    add_files("src/add.c")
    add_files("src/subdir/add.c")

target("sub")
    set_kind("static")
    add_files("src/sub.c")
    add_files("src/subdir/sub.c")

target("mul")
    set_kind("static")
    add_deps("add", "sub")
    add_files("src/mul.c")
    set_policy("build.merge_archive", true)

target("test")
    add_deps("mul")
    add_files("src/main.c")

libmul.a 静态库会自动合并 libadd.a 和 libsub.a 两个子依赖的静态库。

build.ccache

Xmake 默认是开启内置的编译缓存的,通过设置这个策略,可以显式禁用缓存。

set_policy("build.ccache", false)

当然,我们也可以命令行去禁用它。

$ xmake f --ccache=n

或者

$ xmake f --policies=build.ccache:n

build.warning

默认编译通常不会实时回显警告输出,我们通常需要使用 xmake -w 开启,或者通过 xmake g --build_warning=y 来全局开启它。

现在,我们也可以在 xmake.lua 配置中去默认启用警告回显输出。

set_policy("build.warning", true)
set_warnings("all", "extra")

这个时候,即使我们执行 xmake 命令,也能直接回显警告输出。

build.optimization.lto

2.6.9 版本 xmake 改进了对 LTO 链接时优化的支持,对 gcc/clang/msvc 等不同平台下都进行了适配,只需要启用这个策略,就能对特定 target 开启 LTO。

set_policy("build.optimization.lto", true)

我们也可以通过命令行选项快速开启。

$ xmake f --policies=build.optimization.lto

2.7.7 版本可以通过这个配置,显示开启对特定目标的设备链接。

这通常用于 Cuda 项目的构建,以及非 Cuda binary/shared 依赖 Cuda static 目标的情况,这个时候,Cuda static 目标就需要显示配置这个,开启设备链接。

target("test")
    set_kind("static")
    set_policy("build.cuda.devlink", true)

而默认 Cuda binary/shared 是开启 devlink 的,我们也可以通过策略显示禁用它。

关于这个的详细背景说明,见:#1976

build.sanitizer.address

Address Sanitizer(ASan)是一个快速的内存错误检测工具,由编译器内置支持,通常我们需要在编译和链接的 flags 中同时配置 -fsanitize-address 才能正确开启。

而我们可以通过开启这个策略,就可以快速全局启用它,这会使得编译出来的程序,直接支持 ASan 检测。

例如,我们可以通过命令行的方式去启用:

$ xmake f --policies=build.sanitizer.address

也可以通过接口配置去全局启用:

set_policy("build.sanitizer.address", true)

当然,我们也可以单独对某个特定的 target 去配置开启。

另外,如果全局配置它,我们就可以同时对所有依赖包也生效。

set_policy("build.sanitizer.address", true)

add_requires("zlib")
add_requires("libpng")

它等价于,对每个包依次设置 asan 配置。

add_requires("zlib", {configs = {asan = true}})
add_requires("libpng", {configs = {asan = true}})

!> add_rules("mode.asan", "mode.tsan", "mode.ubsan", "mode.msan") 将被废弃,尽可能使用这些新的策略,因为这些构建模式无法同步对依赖包生效。

另外,我们也可以同时生效多个 sanitizer 检测,例如:

set_policy("build.sanitizer.address", true)
set_policy("build.sanitizer.undefined", true)

或者

$ xmake f --policies=build.sanitizer.address,build.sanitizer.undefined

build.sanitizer.thread

build.sanitizer.address 类似,用于检测线程安全问题。

build.sanitizer.memory

build.sanitizer.address 类似,用于检测内存问题。

build.sanitizer.leak

build.sanitizer.address 类似,用于检测内存泄漏问题。

build.sanitizer.undefined

build.sanitizer.address 类似,用于检测 undefined 问题。

build.always_update_configfiles

这个策略用于对 add_configfiles 配置文件的自动生成行为。默认情况下,xmake 仅仅只会在首次 xmake config 时候,或者 xmake.lua 配置有改动的是否,才会触发 configfiles 的重新生成。

之后的每次构建,只要配置没有变化,就不会重新生成 configfiles。

但是,如果我们的 configfiles 中有使用 GIT_COMMIT 等变量,想要每次构建时候,总是重新生成最新的配置,那么可以配置它。

具体使用背景,可以看下:#4747

build.intermediate_directory

配置启用或禁用构建的内部子目录。

默认情况下,执行 xmake 编译项目会自动在 build 目录下根据平台。架构,编译模式生成子目录,分别存储对象文件,目标文件。例如:

build/
└── macosx
    └── x86_64
        └── release
            └─test

如果配置禁用此策略,那么生成的产物将会直接生成到 build 根目录下。变成:

build/
└─ test

build.rpath

配置启用或者禁用构建时的 target rpath 设置。

默认情况下,如果 target(foo) 依赖动态库 bar,那么生成的 foo 可执行文件会自动加上 bar 的 rpath,这能保证用户直接执行 foo 程序,也能正确找到 bar。

如果你想禁用这个行为,可以显式配置禁用它。

install.rpath

尽管构建后的程序,会被设置 rpath,但是当 xmake install 安装后,它构建时候的 rpath 就不一定完全适用了,因此 xmake 会自动修改调整 rpath,使得安装后的程序,同样可以找到它的依赖库。

不过前提是,用户自己先得通过 add_rpathdirs("/xxx", {installonly = true}) 去配置独立的安装 rpath。

而我们也可以通过这个 policy 去禁用默认的安装阶段 rpath 设置行为。

run.autobuild

这个策略用于调整 xmake run 的行为,默认情况下,执行 xmake run 并不会自动构建目标程序,如果程序还没被编译,就是提示用户手动构建一下。

而开启这个策略,我们就可以在运行程序前,先自动构建对应的目标程序。

$ xmake f --policies=run.autobuild
$ xmake run

如果想要全局生效这个策略,可以全局开启它。

$ xmake g --policies=run.autobuild

preprocessor.linemarkers

通常用户编译缓存中,预处理器的生成策略,默认开启,如果配置关闭这个策略,那么缓存生成的预处理文件内容将不包含 linemarkers 信息,这会极大减少预处理文件大小。
也会提升缓存的处理效率,但是缺点就是会丢失源码行信息,如果遇到编译错误,将无法看到准确的出错代码行。

preprocessor.gcc.directives_only

这也是用于预处理器的策略,默认开启,这会提升 gcc 下编译缓存预处理的效率,但是如果源文件中包含 __DATE__, __TIME__ 等宏,就会导致缓存出现不一致。

因此,可以根据自身工程代码,按需关闭此策略,确保生成的结果一致。

package.requires_lock

可用于开启 add_requires() 引入的依赖包的版本锁定。

具体看下:依赖包的锁定和升级

package.precompiled

可用于禁用 windows 下预编译依赖包的获取。

package.fetch_only

如果开启这个策略,那么所有的依赖包仅仅只会从系统获取,不会从远程下载安装。

package.install_only

如果开启这个策略,那么所有的依赖包仅仅只会走远程下载安装,不会从系统查找获取。

package.librarydeps.strict_compatibility

默认禁用,如果启用它,那么当前包和它的所有库依赖包之间会保持严格的兼容性,任何依赖包的版本更新,都会强制触发当前包的重新编译安装。

以确保所有的包都是二进制兼容的,不会因为某个依赖包接口改动,导致和其他已被安装的其他包一起链接时候,发生链接和运行错误。

package("foo")
    add_deps("bar", "zoo")
    set_policy("package.librarydeps.strict_compatibility", true)

例如,如果 bar 或者 zoo 的版本有更新,那么 foo 也会重新编译安装。

package.strict_compatibility

默认禁用,如果启用它,那么当前包和其他所有依赖它的包之间会保持严格的兼容性,这个包的版本更新,都会强制触发其他父包的重新编译安装。

以确保所有的包都是二进制兼容的,不会因为某个依赖包接口改动,导致和其他已被安装的其他包一起链接时候,发生链接和运行错误。

package("foo")
    set_policy("package.strict_compatibility", true)

package("bar")
    add_deps("foo")

package("zoo")
    add_deps("foo")

例如,如果 foo 的版本有更新,那么 bar 和 zoo 都会被强制重新编译安装。

package.install_always

每次运行 xmake f -c 重新配置的时候,总是会重新安装包,这对于本地第三方源码包集成时候比较有用。

因为,用户可能随时需要修改第三方源码,然后重新编译集成它们。

之前只能通过每次修改包版本号,来触发重新编译,但是有了这个策略,就能每次都会触发重编。

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

package("foo")
    add_deps("cmake")
    set_sourcedir(path.join(os.scriptdir(), "foo"))
    set_policy("package.install_always", true)
    on_install(function (package)
        local configs = {}
        table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release"))
        table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
        import("package.tools.cmake").install(package, configs)
    end)
    on_test(function (package)
        assert(package:has_cfuncs("add", {includes = "foo.h"}))
    end)
package_end()

add_requires("foo")

target("demo")
    set_kind("binary")
    add_files("src/main.c")
    add_packages("foo")

package.download.http_headers

设置包下载的 http headers

如果有些包的 url 下载,需要设置特定 http headers,才能通过下载,可以通过这个策略来指定。

package("xxx")
    set_policy("package.download.http_headers", "TEST1: foo", "TEST2: bar")

我们也可以设置指定的 urls 的 http headers:

add_urls("https://github.com/madler/zlib/archive/$(version).tar.gz", {
    http_headers = {"TEST1: foo", "TEST2: bar"}
})

windows.manifest.uac

通过这个策略,我们可以快速方便的设置并启用 Windows UAC。

它支持以下几个 Level:

Level Flag
invoker asInvoker
admin requireAdministrator
highest highestAvailable

例如:

set_policy("windows.manifest.uac", "admin")

它等价于设置

if is_plat("windows") then
    add_ldflags("/manifest:embed", {"/manifestuac:level='requireAdministrator' uiAccess='false'"}, {force = true, expand = false})
end

但是更加方便简洁,并且不需要判断平台,其他平台自动忽略。

windows.manifest.uac.ui

设置 Windows UAC 的 uiAccess,如果没有设置它,默认是 false。

set_policy("windows.manifest.uac.ui", true)