---
title: xmake v2.5.7 发布，包依赖锁定和 Vala/Metal 语言编译支持
tags: [xmake, lua, C/C++, lock, package, vala]
date: 2021-08-29
author: Ruki
outline: deep
---

## 新特性介绍

### 新增 Vala 语言支持

这个版本，我们已经可以初步支持构建 Vala 程序，只需要应用 `add_rules("vala")` 规则。

同时，我们需要添加一些依赖包，其中 glib 包是必须的，因为 vala 自身也会用到它。

`add_values("vala.packages")` 用于告诉 valac，项目需要哪些包，它会引入相关包的 vala api，但是包的依赖集成，还是需要通过 `add_requires("lua")` 下载集成。

例如：

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

add_requires("lua", "glib")

target("test")
    set_kind("binary")
    add_rules("vala")
    add_files("src/*.vala")
    add_packages("lua", "glib")
    add_values("vala.packages", "lua")
```

更多例子：[Vala examples](https://github.com/xmake-io/xmake/tree/master/tests/projects/vala)

### 新增包依赖锁定支持

这个特性类似 npm 的 package.lock, cargo 的 cargo.lock。








比如，我们引用一些包，默认情况下，如果不指定版本，那么 xmake 每次都会自动拉取最新版本的包来集成使用，例如：

```lua
add_requires("zlib")
```

但如果上游的包仓库更新改动，比如 zlib 新增了一个 1.2.11 版本，或者安装脚本有了变动，都会导致用户的依赖包发生改变。

这容易导致原本编译通过的一些项目，由于依赖包的变动出现一些不稳定因素，有可能编译失败等等。

为了确保用户的项目每次使用的包都是固定的，我们可以通过下面的配置去启用包依赖锁定。

```lua
set_policy("package.requires_lock", true)
```

这是一个全局设置，必须设置到全局根作用域，如果启用后，xmake 执行完包拉取，就会自动生成一个 `xmake-requires.lock` 的配置文件。

它包含了项目依赖的所有包，以及当前包的版本等信息。

```lua
{
    __meta__ = {
        version = "1.0"
    },
    ["macosx|x86_64"] = {
        ["cmake#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "4498f11267de5112199152ab030ed139c985ad5a",
                url = "https://github.com/xmake-io/xmake-repo.git"
            },
            version = "3.21.0"
        },
        ["glfw#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "eda7adee81bac151f87c507030cc0dd8ab299462",
                url = "https://github.com/xmake-io/xmake-repo.git"
            },
            version = "3.3.4"
        },
        ["opengl#31fecfc4"] = {
            repo = {
                branch = "master",
                commit = "94d2eee1f466092e04c5cf1e4ecc8c8883c1d0eb",
                url = "https://github.com/xmake-io/xmake-repo.git"
            }
        }
    }
}
```

当然，我们也可以执行下面的命令，强制升级包到最新版本。

```console
$ xmake require --upgrade
upgrading packages ..
  zlib: 1.2.10 -> 1.2.11
1 package is upgraded!
```

### option 支持代码片段的运行时检测

option 本身有提供 `add_csnippets/add_cxxsnippets` 两个接口，用于快速检测特定一段 c/c++ 代码是否通过编译，如果编译通过就会启用对应 option 选项。

但之前的版本仅仅只能提供编译期检测，而新版本中，我们还新增了运行时检测支持。

我们可以通过设置 `{tryrun = true}` 和 `{output = true}` 两个参数，用于尝试运行检测和捕获输出。

#### 尝试运行检测

设置 tryrun 可以尝试运行来检测

```lua
option("test")
    add_cxxsnippets("HAS_INT_4", "return (sizeof(int) == 4)? 0 : -1;", {tryrun = true})
```

如果编译运行通过，test 选项就会被启用。

#### 运行时检测并捕获输出

设置 output 也会尝试去检测，并且额外捕获运行的输出内容。

```lua
option("test")
    add_cxxsnippets("INT_SIZE", 'printf("%d", sizeof(int)); return 0;', {output = true, number = true})
```

如果编译运行通过，test 选项就会被启用，同时能获取到对应的输出内容作为 option 的值。

注：设置为捕获输出，当前 option 不能再设置其他 snippets

我们也可以通过 `is_config` 获取绑定到option的输出。

```lua
if is_config("test", "8") tben
    -- xxx
end
```

另外，我们也对 `includes("check_csnippets")` 的辅助检测接口，也做了改进来支持运行时检测。

```lua
includes("check_csnippets.lua")

target("test")
    set_kind("binary")
    add_files("*.c")
    add_configfiles("config.h.in")

    check_csnippets("HAS_INT_4", "return (sizeof(int) == 4)? 0 : -1;", {tryrun = true})
    check_csnippets("INT_SIZE", 'printf("%d", sizeof(int)); return 0;', {output = true, number = true})
    configvar_check_csnippets("HAS_LONG_8", "return (sizeof(long) == 8)? 0 : -1;", {tryrun = true})
    configvar_check_csnippets("PTR_SIZE", 'printf("%d", sizeof(void*)); return 0;', {output = true, number = true})
```

如果启用捕获输出，`config.h.in` 的 `${define PTR_SIZE}` 会自动生成 `#define PTR_SIZE 4`。

其中，`number = true` 设置，可以强制作为 number 而不是字符串值，否则默认会定义为 `#define PTR_SIZE "4"`

### 快速内嵌二进制资源文件到代码

我们可以通过 `utils.bin2c` 规则，在项目中引入一些二进制文件，并见他们作为 c/c++ 头文件的方式提供开发者使用，获取这些文件的数据。

比如，我们可以在项目中，内嵌一些 png/jpg 资源文件到代码中。

```lua
target("console")
    set_kind("binart")
    add_rules("utils.bin2c", {extensions = {".png", ".jpg"}})
    add_files("src/*.c")
    add_files("res/*.png", "res/*.jpg")
```

注：extensions 的设置是可选的，默认后缀名是 .bin

然后，我们就可以通过 `#include "filename.png.h"` 的方式引入进来使用，xmake 会自动帮你生成对应的头文件，并且添加对应的搜索目录。

```c
static unsigned char g_png_data[] = {
    #include "image.png.h"
};

int main(int argc, char** argv)
{
    printf("image.png: %s, size: %d\n", g_png_data, sizeof(g_png_data));
    return 0;
}
```

生成头文件内容类似：

```console
cat build/.gens/test/macosx/x86_64/release/rules/c++/bin2c/image.png.h
  0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x78, 0x6D, 0x61, 0x6B, 0x65, 0x21, 0x0A, 0x00
```

### 新增 iOS/macOS 应用 Metal 编译支持

我们知道 `xcode.application` 规则可以编译 iOS/macOS 应用程序，生成 .app/.ipa 程序包，并同时完成签名操作。

不过之前它不支持对带有 .metal 代码的编译，而新版本中，我们新增了 `xcode.metal` 规则，并默认关联到 `xcode.application` 规则中去来默认支持 metal 编译。

xmake 会自动编译 .metal 然后打包生成 default.metallib 文件，并且自动内置到 .app/.ipa 里面。

如果用户的 metal 是通过 `[_device newDefaultLibrary]` 来访问的，那么就能自动支持，就跟使用 xcode 编译一样。

这里是我们提供的一个完整的：[项目例子](https://github.com/xmake-io/xmake/blob/master/tests/projects/objc/metal_app/xmake.lua)。

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

target("HelloTriangle")
    add_rules("xcode.application")
    add_includedirs("Renderer")
    add_frameworks("MetalKit")
    add_mflags("-fmodules")
    add_files("Renderer/*.m", "Renderer/*.metal") ------- 添加 metal 文件
    if is_plat("macosx") then
        add_files("Application/main.m")
        add_files("Application/AAPLViewController.m")
        add_files("Application/macOS/Info.plist")
        add_files("Application/macOS/Base.lproj/*.storyboard")
        add_defines("TARGET_MACOS")
        add_frameworks("AppKit")
    elseif is_plat("iphoneos") then
        add_files("Application/*.m")
        add_files("Application/iOS/Info.plist")
        add_files("Application/iOS/Base.lproj/*.storyboard")
        add_frameworks("UIKit")
        add_defines("TARGET_IOS")
```

比如，在 macOS 上，编译运行后，就会通过 metal 渲染出需要的效果。

![](/assets/img/posts/xmake/xmake-metal.png)

如果，我们的项目没有使用默认的 metal library，我们也可以通过上面提到的 `utils.bin2c` 规则，作为源文件的方式内嵌到代码库中，例如：

```lua
add_rules("utils.bin2c", {extensions = ".metal"})
add_files("Renderer/*.metal")
```

然后代码中，我们就能访问了：

```c
static unsigned char g_metal_data[] = {
    #include "xxx.metal.h"
};

id<MTLLibrary> library = [_device newLibraryWithSource:[[NSString stringWithUTF8String:g_metal_data]] options:nil error:&error];
```

### 改进 add_repositories

如果我们通过内置在项目中的本地仓库，我们之前是通过 `add_repositories("myrepo repodir")` 的方式来引入。

但是，它并不像 `add_files()` 那样是基于当前 xmake.lua 文件目录的相对目录，也没有路径的自动转换，因此容易遇到找不到 repo 的问题。

因此，我么你改进了下它，可以通过额外的 rootdir 参数指定对应的根目录位置，比如相对当前 xmake.lua 的脚本目录。

```lua
add_repositories("myrepo repodir", {rootdir = os.scriptdir()})
```

### os.cp 支持符号链接

之前的版本，`os.cp` 接口不能很好的处理符号链接的复制，他会自动展开链接，复制实际的文件内容，只会导致复制后，符号链接丢失。

如果想要复制后，原样保留符号链接，只需要设置下参数：`{symlink = true}`

```lua
os.cp("/xxx/symlink", "/xxx/dstlink", {symlink = true})
```

### 更方便地编译自动生成的代码

有时候，我们会有这样一个需求，在编译前，自动生成一些源文件参与后期的代码编译。但是由于 `add_files` 添加的文件在执行编译时候，就已经确定，无法在编译过程中动态添加它们（因为需要并行编译）。

因此，要实现这个需求，我们通常需要自定义一个 rule，然后里面主动调用编译器模块去处理生成代码的编译，对象文件的注入，依赖更新等一系列问题。

这对于 xmake 开发者本身没什么大问题，但是对于用户来说，这还是比较繁琐了，不好上手。

新版本中，我们改进了对 `add_files` 的支持，并添加了 `{always_added = true}` 配置来告诉 xmake 我们始终需要添加指定的源文件，即使它还不存在。

这样我们就可以依靠xmake的默认编译过程来编译自动生成的代码了，像这样：

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

target("autogen_code")
    set_kind("binary")
    add_files("$(buildir)/autogen.cpp", {always_added = true})
    before_build(function (target)
        io.writefile("$(buildir)/autogen.cpp", [[
#include <iostream>

using namespace std;

int main(int argc, char** argv)
{
    cout << "hello world!" << endl;
    return 0;
}
        ]])
    end)
```

都不需要额外的 rule 定义，只需要保证编译顺序，在正确的阶段生成代码文件就可以了。

但是，我们也需要注意，由于当前自动生成的源文件可能还不存在，我们不能在 `add_files` 里面使用模式匹配，只能显式添加每个源文件路径。

## 更新内容

### 新特性

* [#1534](https://github.com/xmake-io/xmake/issues/1534): 新增对 Vala 语言的支持
* [#1544](https://github.com/xmake-io/xmake/issues/1544): 添加 utils.bin2c 规则去自动从二进制资源文件产生 .h 头文件并引入到 C/C++ 代码中
* [#1547](https://github.com/xmake-io/xmake/issues/1547): option/snippets 支持运行检测模式，并且可以获取输出
* [#1567](https://github.com/xmake-io/xmake/issues/1567): 新增 xmake-requires.lock 包依赖锁定支持
* [#1597](https://github.com/xmake-io/xmake/issues/1597): 支持编译 metal 文件到 metallib，并改进 xcode.application 规则去生成内置的 default.metallib 到 app

### 改进

* [#1540](https://github.com/xmake-io/xmake/issues/1540): 更好更方便地编译自动生成的代码
* [#1578](https://github.com/xmake-io/xmake/issues/1578): 改进 add_repositories 去更好地支持相对路径
* [#1582](https://github.com/xmake-io/xmake/issues/1582): 改进安装和 os.cp 支持符号链接

### Bugs 修复

* [#1531](https://github.com/xmake-io/xmake/issues/1531): 修复 targets 加载失败的错误信息提示错误
