back

Xmake 常见问题解答

如果在本文没找到解答,可以去 github 提 issue 和讨论区搜索。

如果是官方包管理的问题,或者想请求加入一些包,去 xmake-repo 提 issue。

提 issue 请附带 log,xmake -vD命令可以输出详细的 log 信息。

基础

避免外网访问

xmake 很多操作需要访问 github(拉取 xmake-repo/build-artifact),而在公司内网开发是不需要这些操作的。

xmake g --network=private

参考:

Windows 上 Qt 项目控制台没有输出

使用:

add_ldflags("/subsystem:console")

xmake 的 qt rule 里会检测 ldflags 里有没有/subsystem,如果没有就默认/subsystem:windows

给单独源文件添加参数

add_files("test/*.c", "test2/test2.c", {defines = "TEST2", languages = "c99", includedirs = ".", cflags = "-O0"})
-- 强制禁用 cxflags,cflags 等编译选项的自动检测
add_files("src/*.c", {force = {cxflags = "-DTEST", mflags = "-framework xxx"}})

给指定文件添加 config

target("test")
    add_files("test/*.cpp", {foo = 1})

    on_config(function (target)
        -- configs = {foo = 1}
        local configs = target:fileconfig("src/main.cpp")
    end)

找不到 vs 工具链

在 Visual Studio Installer 修改了配置后,清除全局缓存。

xmake g -c
xmake f -c

参考:

指定 vs 版本

xmake f --vs=2017 --vs_toolset=14.0 --vs_sdkver=10.0.15063.0

IDE(vscode/vs)不显示头文件

给 target 加上add_headerfiles("**.h")

终端不支持色彩输出

切换主题

xmake g --theme=plain

如何在脚本域添加文件

只能在on_load添加,并且需要确保路径正确。

因为描述域的add_files()和脚本域的target:add("files")有一些差异,并不会做路径转换。

后者接口可能会在任何地方使用,所以路径只能用os.projectdir或者os.scriptdir进行拼接路径。

此外,需要确保该 target 已经应用了一个编译规则,否则不会根据后缀名去添加文件(可能会出现error: unknown source file: xxx.cpp)。

比如你在脚本域添加 .cpp 文件,那么首先在描述域添加add_rules("c++")

增量配置

假如我们做以下操作:

xmake f -m debug --test=y
xmake f --test=n

你会发现当前mode又切换了为默认配置的release

xmake 暂时没有支持增量配置,这里有过相关讨论。

但我们也有解决的方法,使用SirLynix写的插件。

xmake plugin --install https://github.com/SirLynix/xmake-plugins

xmake config-update或者xmake cu代替原来的xmake config或者xmake f

在不同脚本域之间传递数据

  • memcache
target("foo")
    on_load(function (target)
        import("core.cache.memcache")
        memcache.set("cachename", "key", "value")
    end)

target("bar")
    on_load(function (target)
        import("core.cache.memcache")
        local memcache.get("cachename", "key")
    end)
  • target:data()
target("foo")
    on_load(function (target)
        target:data_set("key", "value")
    end)

target("bar")
    on_load(function (target)
        import("core.project.project")
        local foo = project.targets()["foo"]
        local value = foo:data("key")
    end)
  • 单个脚本文件
target("foo")
    on_load(function (target)
          import("load_module").load_foo(target)
    end)

target("bar")
    on_load(function (target)
          import("load_module").load_bar(target)
    end)

load_module.lua

function load_foo(target)
    _g.key = "value"
end

function load_bar(target)
    local value = _g.key
end

参考:

自动化

如何一键编译运行

2.8.3 版本以前:

在 target 下加入以下代码:

before_run(function (target)
    os.execv("xmake build " .. target:name())
end)

-- xmake run target

2.8.3 版本以后:

$ xmake g --policies=run.autobuild

vs 工程

  • 自动更新 sln。
add_rules("plugin.vsxmake.autoupdate")
  • 使用add_filegroups可以打平嵌套过深的目录树,可读性更高。

单元测试

2.8.4 版本后官方支持单元测试,具体例子看:https://github.com/xmake-io/xmake/issues/3381

以前的版本可以考虑这样写:

rule("module.test")
    on_load(function (target)
        if not has_config("test") then
            target:set("enabled", false)
            return
        end

        target:set("policy", "build.warning", true)
        target:set("rundir", "$(projectdir)")
        target:set("group", "test")
        -- 选择你想要的单元测试库
        target:add("packages", "gtest")
    end)
rule_end()

add_rules("module.test")
-- 假设 test 目录下每个 cpp 文件都有自己的 main 函数
for _, file in ipairs(os.files("test/*.cpp")) do
    local name = path.basename(file)

    target("test." .. name)
        set_kind("binary")
        add_files(file)
    target_end()
end

然后我们使用下面命令:

xmake f -m debug --test=y
xmake build -g test
xmake run -g test

如果想一行xmake test命令搞定,使用task

task("test")
    on_run(function ()
        os.exec("xmake f -m debug --test=y")
        os.exec("xmake build -g test")
        os.exec("xmake run -g test")
    end)

    set_menu{}

自定义检测行为

xmake 在检测各种工具、配置等等信息时都会有缓存,我们可以自定义一个检测行为并对其缓存。

on_config(function (target)
    import("core.cache.detectcache")
    local has_deprecated_key = "custom.has_deprecated"
    local has_deprecated = detectcache:get(has_deprecated_key)
    if not has_deprecated then
        has_deprecated = target:check_csnippets({
            has_deprecated = [[
            #define TEST __declspec(deprecated)
            int somefunc() { return 0; }
            int main() { return somefunc();}
        ]]
        })
        detectcache:set(has_deprecated_key, has_deprecated)
        detectcache:save()
    end
    -- has_deprecated is now cached
end)

包管理

依赖同一个包多个版本

有依赖包 A 和 B,A 包依赖 1.0.0 版本的 C 包,B 包依赖 1.1.0 版本的 C 包,如果继续编译的话,可能会链接失败,解决方法就是打平依赖,只依赖 C 包同一个版本。

add_requireconfs("B.C", {version = "1.0.0", override = true})

这里把 C 包统一到 1.0.0 版本。

参考

vcpkg 包不会自动处理依赖

暂时不支持,最好去用官方包

参考

如何调试包源码

xmake 早期是保留源码的,但每次安装都保留源码和编译产物,容易把磁盘用满,所以现在优化了,不再保留。

# -d 指定源码覆盖安装,本地源码调试包
xrepo install -m debug -d package_sourcedir xxx

有些远程包为什么没有拉预编译版本

众所周知 c++ 编译器编译出的二进制有 abi 问题,同一个编译器不同版本都可能不兼容,所以当预编译库的编译器版本(github ci 编译)和本地编译器版本不一致的时候,会拉取源码进行编译。

有些库很大,编译需要很久,可以考虑下面方案:

  • 比较推荐通过set_base("package")继承包,然后覆盖包的一些设置,跳过 xmake 的检查直接安装二进制包。这里可能需要对 xmake 有一定的基础,不过完成后都是自动化,这是值得的。

  • 手动下载预编译包到本地包仓库

假设预编译包目录和xmake.lua同一个目录,使用on_fetch进行配置。

这里拿 glfw 作为参考。

我这里下载并解压了glfw-3.3.8.bin.WIN64.zip,然后在xmake.lua这样写:

-- 演示操作,不一定正确
package("glfw")
    on_load(function (package)
        -- set package dir
        package:set("installdir", path.join(os.scriptdir(), "glfw-3.3.8.bin.WIN64"))
    end)

    on_fetch(function (package)
        -- add dll
        package:addenv("PATH", package:installdir("lib-vc2022"))

        local result = {}
        if is_plat("windows") then
            result.syslinks =  {"user32", "shell32", "gdi32"}
        end

        result.links = {"glfw3", "glfw3_mt", "glfw3dll"}
        result.includedirs = package:installdir("include")
        result.linkdirs = package:installdir("lib-vc2022")
        return result
    end)
package_end()

使用:

set_runtimes("MD")
add_requires("glfw")

target("test")
    set_kind("binary")
    add_files("*.cpp")
    add_packages("glfw")

其他

xmake 源码怎么看

有一篇老文章可以看看 -> xmake 源码架构剖析

定制 api

参考一下这个 pr 是怎么写的:Add set_encodings api to set source/target encoding