---
title: xmake进阶之简化你的构建描述
tags: [xmake, lua, 工程描述, xmake.lua, 简化]
date: 2018-06-08
author: Ruki
outline: deep
---

[xmake](https://github.com/xmake-io/xmake)的初衷就是为了让用户能够用最简单直接的方式去描述工程，提供跨平台项目构建，因此，`简洁，灵活` 是xmake.lua的核心设计思想。

通过之前的那篇文章:[xmake入门，构建项目原来可以如此简单](http://tboox.org/cn/2018/03/26/build-project-so-simply/)，我们对如何使用xmake去构建项目有了大概的了解，并且能够编写一些简单的xmake.lua去描述项目，例如:

```lua
target("test")
    set_kind("binary")
    add_files("src/*.c")
```

但是平常我们实际的项目维护，不可能这么简单，会有各种各样的配置需求，例如: 添加每个平台特有的flags，处理debug/release编译模式，多个target的依赖编译等等，在混杂了用户各种配置需求后，xmake.lua的维护很容易变得很臃肿，可读性变差。

因此本文会介绍一些常用的编写模式，去尽可能的利用xmake的设计特性，去简化对工程的描述，提高可读性和易维护性，避免用户因为不了解xmake而导致的一些错误写法。

## 在根域添加通用配置

xmake的配置关系是根据tree结构继承，子xmake.lua会集成父xmake.lua中的配置，同一个xmake.lua中，所有target的配置会集成根作用域的配置，因此一些通用配置，可以放置在根域，避免重复设置，例如:








#### 简化前

```lua
target("test1")
    set_kind("binary")
    add_files("src1/*.c")
    if is_mode("debug") then
        set_symbols("debug")
        set_optimize("none")
    end
    if is_mode("release") then
        set_symbols("hidden")
        set_optimize("fastest")
        set_strip("all")
    end
    if is_plat("linux") then
        add_defines("PLAT_IS_LINUX")
    end
    if is_plat("macosx") then
        add_defines("PLAT_IS_MACOSX")
    end
    if is_plat("android") then
        add_defines("PLAT_IS_ANDROID")
    end

target("test2")
    set_kind("binary")
    add_files("src2/*.c")
    if is_mode("debug") then
        set_symbols("debug")
        set_optimize("none")
    end
    if is_mode("release") then
        set_symbols("hidden")
        set_optimize("fastest")
        set_strip("all")
    end
    if is_plat("linux") then
        add_defines("PLAT_IS_LINUX")
    end
    if is_plat("macosx") then
        add_defines("PLAT_IS_MACOSX")
    end
    if is_plat("android") then
        add_defines("PLAT_IS_ANDROID")
    end

```

#### 简化后

```lua
if is_mode("debug") then
    set_symbols("debug")
    set_optimize("none")
end
if is_mode("release") then
    set_symbols("hidden")
    set_optimize("fastest")
    set_strip("all")
end
if is_plat("linux") then
    add_defines("PLAT_IS_LINUX")
end
if is_plat("macosx") then
    add_defines("PLAT_IS_MACOSX")
end
if is_plat("android") then
    add_defines("PLAT_IS_ANDROID")
end

target("test1")
    set_kind("binary")
    add_files("src1/*.c")

target("test2")
    set_kind("binary")
    add_files("src2/*.c")
```

把通用的设置放到根域后，可以避免对每个target的重复设置，target越多，效果越明显。

## 利用rule去简化常用配置

对于一些常用的配置，xmake最新版本中提供了内置规则去简化它，例如: `mode.debug`, `mode.release`规则等，提供对编译模式的内置配置处理，我们还是以刚才的代码为例，看看应用规则后的效果: 

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

if is_plat("linux") then
    add_defines("PLAT_IS_LINUX")
end
if is_plat("macosx") then
    add_defines("PLAT_IS_MACOSX")
end
if is_plat("android") then
    add_defines("PLAT_IS_ANDROID")
end

target("test1")
    set_kind("binary")
    add_files("src1/*.c")

target("test2")
    set_kind("binary")
    add_files("src2/*.c")
```

看上去，比刚才的结果更加简化了不少，其中`mode.release`规则被应用后，相当于配置了:

```lua
if is_mode("release") then
    set_symbols("hidden")
    set_optimize("fastest")
    set_strip("all")
end
```

这其实有点像c/c++中的宏，不过rule更加强大，因为它还可以对一个target同时叠加多个rule，甚至用户可以自定义一些rule去简化自己的常用配置，或者自定义扩展构建规则。

具体对rule的使用说明，我后续会有单独的文章来介绍，如果用户感兴趣的话，可以先看下相关文档深入了解下，里面还有很多xmake提供的内建规则: [构建规则文档](https://xmake.io/zh/)

## 利用内建变量条件配置

上述代码中，其实还是有许多冗余的地方，例如:

```lua
if is_plat("linux") then
    add_defines("PLAT_IS_LINUX")
end
```

这里每次都判断下平台，仅仅只是为了设置一个宏定义的话，没必要这么写，直接使用xmake提供的内建变量`$(plat)`会更加简单直接，例如:

```lua
add_defines("PLAT_IS_$(plat)")
```

不过，这里还不是完全一致，我们需要的是大写的定义，因此可以改成:

```lua
add_defines("PLAT_IS_$(plat:upper)")
```

最后贴下完整的简化代码:

```lua
add_rules("mode.debug", "mode.release")
add_defines("PLAT_IS_$(plat:upper)")

target("test1")
    set_kind("binary")
    add_files("src1/*.c")

target("test2")
    set_kind("binary")
    add_files("src2/*.c")
```

相比最初的配置，现在已经相当精简了，并且更加可读，易维护。如果想了解更多的内建变量，请参考文档：[内建变量](https://xmake.io/zh/)

## 利用lua脚本简化配置

很多时候，会有一些重复的逻辑配置，并且包含了一些用户的配置逻辑在里面，单纯用内建变量满足不了需求，这个时候就可以通过写一小段lua脚本，或者封装个lua函数去简化它们。

例如，我们想定义好几个target，但是他们的编译选项完全相同，仅仅是源码所在目录不同，就比如之前的代码，我们可以继续简化：

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

add_defines("PLAT_IS_$(plat:upper)")

for _, id in ipairs({"1", "2"}) do
    target("test" .. id)
        set_kind("binary")
        add_files("src" .. id .. "/*.c")
end
```

或者通过定义个function来实现：

```lua
function define_target(...)
    for _, id in ipairs({...}) do
        target("test" .. id)
            set_kind("binary")
            add_files("src" .. id .. "/*.c")
    end
end

add_rules("mode.debug", "mode.release")
add_defines("PLAT_IS_$(plat:upper)")

define_target(1, 2)
```

注：这种方式，不能滥用，对于确实有太多的重复配置的时候，可以通过脚本适当简化下，使用不当也会起一些反效果，反而令整体可读性降低。

## 利用includes分离单个xmake.lua

上节介绍的方法去简化target的定义，可读性还不是很好，对于单个xmake.lua，如果充斥太多target，有可能会变得非常臃肿，这个时候，我更倾向于使用includes去分离`xmake.lua`，每个子目录提供一个单独的`xmake.lua`

假如有下面的目录结构：

```
├── src1
│   └── main.c
├── src2
│   └── main.c
└── xmake.lua
```

我们可以在每个src子目录下，单独提供一个子`xmake.lua`去维护：

```
├── src1
│   ├── main.c
│   └── xmake.lua
├── src2
│   ├── main.c
│   └── xmake.lua
└── xmake.lua
```

然后根`xmake.lua`中可以通过includes去包含子目录的`xmake.lua`配置，之前的配置代码可以简化成：

```lua
add_rules("mode.debug", "mode.release")
add_defines("PLAT_IS_$(plat:upper)")

includes("src1", "src2")
```

然后子`xmake.lua`中，仅对当前目录下的源码提供一个对应的target，比如`src1/xmake.lua`的内容如下：

```lua
target("test1")
    set_kind("binary")
    add_files("*.c")
```

另外，根`xmake.lua`的配置会自动继承给子配置，不需要再去设置一遍，除非有当前子target特有的配置，那么可以在子`xmake.lua`里面单独设置。

## 利用文件匹配模式简化源文件配置

`add_files`提供了强大的文件匹配模式去简化源文件的添加，用户不需要每次单独添加一个文件，这样太过繁琐：

```lua
target("test")
    add_files("src/test1.c")
    add_files("src/test2.c")
    add_files("src/subdir/test1.c")
    add_files("src/subdir/test2.c")
```

通过匹配模式，我们可以递归添加所有相关源文件：

```lua
target("test")
    add_files("src/**.c")
```

其中通配符*表示匹配当前目录下文件，而**则匹配多级目录下的文件。我们再贴一些例子代码，直观感受下：

```lua
add_files("src/test_*.c")
add_files("src/xxx/**.cpp")
add_files("src/asm/*.S", "src/objc/**/hello.m")
```

`add_files`不仅可以匹配文件，还有可以在添加文件同时，过滤排除指定模式的一批文件，例如：

```lua
-- 递归添加src下的所有c文件，但是不包括src/impl/下的所有c文件
add_files("src/**.c|impl/*.c")

-- 添加src下的所有cpp文件，但是不包括src/test.cpp、src/hello.cpp以及src下所有带xx_前缀的cpp文件
add_files("src/*.cpp|test.cpp|hello.cpp|xx_*.cpp")
```

我们也可以通过`del_files`接口，从前面`add_files`接口添加的文件列表中，删除指定的文件，例如：

```lua
target("test")
    add_files("src/*.c")
    del_files("src/test.c")
```

灵活合理运用[add_files](https://xmake.io/zh/)和[del_files](https://xmake.io/zh/)，我们可以极大程度的简化工程源码的配置管理。

## 利用内置配置简化flags设置

对于一些常用的flags设置，例如：`-O3`, `-g`, `-std=c++11`等编译选项，xmake提供了一些更加通用内置的配置，来简化设置，用户不需要再去考虑不同平台、不同编译器上对应的这些flags的一些差异性，只需要设置：

```lua
set_optimize("fastest")
set_symbols("debug")
set_languages("cxx11")
set_warnings("all", "error")
```

具体配置说明，以及目前提供的配置值，都可以去看下相关文档，里面有详细说明：[内置配置说明](https://xmake.io/zh/)