1. Blade简介
██████╗ ██╗ █████╗ ██████╗ ███████╗
██╔══██╗██║ ██╔══██╗██╔══██╗██╔════╝
██████╔╝██║ ███████║██║ ██║█████╗
██╔══██╗██║ ██╔══██║██║ ██║██╔══╝
██████╔╝███████╗██║ ██║██████╔╝███████╗
╚═════╝ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝
项目地址: blade-build
官方文档: 英文, 中文
什么是Blade
Blade是一个方便易用高性能的现代化代码构建系统,特别适合公司内的大规模代码库的敏捷构建,内置了对多种编程语言及单元测试框架的直接支持。
Blade is an easy-to-use, fast and modern build system for trunk based development in large scale monorepo codebase. It supports building mulitiple programming languages.
为什么要用Blade
Blade 是一个现代构建系统,期望的目标是强大而好用,把程序员从构建的繁琐中解放出来。
Blade主要定位于linux下的大型C++项目,密切配合研发流程,比如单元测试,持续集成,覆盖率统计等。 但像unix下的文本过滤程序一样,保持相对的独立性,可以单独运行。目前重点支持i386/x86_64 Linux,未来可以考虑支持其他的类Unix系统。
- Blade自动维护库与库、程序与库之间的依赖关系,自动分析头文件依赖关系
- 增量构建,即只编译因改动而需要重新构建的代码
- 保证多层间依赖的库能被重新构建
- 简化编译和单测的流程
借用文档中的例子
cc_library(
name = 'foo', // 库名
srcs = ...,
hdrs = ...,
deps = ':common' // 依赖库
)
cc_binary(
name = 'my_app', // 程序名
srcs = ...,
deps = ':foo' // 依赖库
)
例子各个字段的意义会在下文展开
此处想说明的是Blade在分析依赖时的特性。
当构建my_app时,Blade会自动检查其直接依赖的foo库是否需要更新,以及foo库依赖的common库是否需要更新,以此类推。保证了完整更新构建的同时高效构建。
2. Blade安装
依赖安装
Blade 运行时需要以下依赖:
- Linux 或 Mac 操作系统
- Python v2.7+
- Ninja v1.8+
需要注意的是保证本地有python2的环境,如果仅有python3是不够的
Blade 支持 Python 2.7.x,对 python 3.x 的支持还在准备中
同时可以顺带检验一下gcc的版本
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
ninja的安装有两种方式
- GitHub官方项目:地址
- 用包管理工具安装ninja-build
$ sudo apt-get install ninja-build
安装后验证ninja的版本
$ ninja --version
1.10.0
Blade安装
拉取Blade仓库
$ git clone git@github.com:chen3feng/blade-build.git && cd blade-build
切换到v2.0并运行install
$ git checkout v2.0
$ ./install
安装:
$ ./install
Installing vim scripts..., Done.
Installing blade auxiliary tools..., Done.
Installing blade..., Done.
Install success, please relogin or run 'source /home/parallels/.profile' manually to apply
按照提示运行:
$ source /home/parallels/.profile
安装成功:
$ blade -h
usage: blade [-h] [--version] {build,run,test,clean,query,dump} ...
blade <subcommand> [options...] [targets...]
positional arguments:
{build,run,test,clean,query,dump}
Available subcommands
build Build specified targets
run Build and runs a single target
test Build the specified targets and runs tests
clean Remove all blade-created output
query Execute a dependency graph query
dump Dump specified internal information
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
快速测试
根据官方文档的 快速测试部分 创建一个快速测试的项目
也可以直接拉取项目 example 中的 quick-start 进行测试
创建测试目录
$ mkdir quick-start && cd quick-start
$ touch BLADE_ROOT
定义实现 say 库
创建头文件say.h
#pragma once
#include <string>
// Say a message
void Say(const std::string& msg);
创建实现文件say.cpp
#include "say.h"
#include <iostream>
void Say(const std::string& msg) {
std::cout << msg << "!\n";
}
创建BUILD
文件,描述上述文件为’say’库
cc_library(
name = 'say',
hdrs = ['say.h'],
srcs = ['say.cpp'],
)
定义实现 hello 库
创建头文件 hello.h
:
#pragma once
#include <string>
// Say hello to `to`
void Hello(const std::string& to);
创建实现文件 hello.cpp
:
#include "say.h"
void Hello(const std::string& to) {
Say("Hello, " + to);
}
需要注意的是文档中《定义实现 hello 库》下的表述有一定问题
创建 BUILD 文件,把上述文件描述为一个 hello 库
这里并非创建一个新的BUILD文件,而是在上一步的BUILD
文件中追加
cc_library(
name = 'hello',
hdrs = ['hello.h'],
srcs = ['hello.cpp'],
deps = [':say'], // 表示依赖 say 库, ':'前缀表示目标在同一个 BUILD 文件里
)
实现 hello-world 程序
创建 hello-world.cpp
文件:
#include "hello.h"
int main() {
Hello("World");
return 0;
}
在 BUILD
文件中增加编译 hello-world 的规则调用:
cc_binary(
name = 'hello-world',
srcs = ['hello-world.cpp'],
deps = [':hello'],
)
注意规则名是 cc_binary 了,deps 里需要加入对 hello 库的依赖,但是不需要加入对 say 库的依赖,因为这是 hello 的实现细节,hello-world 目标不需要了解,编译和链接时,Blade 会正确处理依赖关系的传递。
测试
创建完后文件目录应该如下:
$ tree
.
├── BLADE_ROOT
├── BUILD
├── hello.cpp
├── hello.h
├── hello-world.cpp
├── say.cpp
└── say.h
测试构建和运行
至此Blade测试成功
3. BUILD文件简述
BUILD语言
Blade 的 DSL 是受限制的 Python 语言
关于Blade的语法,官方文档有比较详细的介绍可以参考:DSL 和 API 模块
拓展阅读:什么是领域专用语言 (DSL)
通用属性
以下内容主要围绕C/C++构建展开,其他类型的功能可以参考文档,这里不一一展开
示例代码:
cc_library(
name = 'string',
srcs = [
'algorithm.cpp',
'format.cpp',
'concat.cpp',
],
hdrs = [
'algorithm.h',
'format.h',
'concat.h',
]
deps = ['//common/base:int'],
)
每个 BUILD文件通过一组目标描述函数描述了一个目标的源文件,所依赖的其他目标,以及其他一些属性。
-
name: 目标的名称,和路径一起组成了target的唯一标识和输出名
-
srcs: source,构建target需要的源文件,官方文档建议一般是存放在当前目录或子目录的文件,同样可以使用通配符
glob
来筛选文件名(glob介绍) -
hdrs: 声明库的公开接口头文件
-
deps: 该对象依赖的其它 targets路径
允许的路径格式:
- “//path/to/dir:name” 其他目录下的target,path为从BLADE_ROOT出发的路径,name为被依赖的目标名。看见就知道在哪里。
- “:name” 当前BUILD文件内的target, path可以省略。
- “#name” 系统库。直接写#跟名字即可,比如#pthread,#z分别相当于链接命令行上的-lpthread和-lz,但是会被传递给依赖这个库的其他目标。
-
cc_library: 同时用于构建静态和动态库,默认只构建静态库
cc_library生成的动态链接库里不包含其依赖的代码,而是包含了对所依赖的库的路径。这些库主要是为了开发环境本地使用(比如运行测试),并不适合部署到生产环境。 如果你需要生成需要在运行时动态加载或者在其他语言中作为扩展调用的动态库,应该使用 cc_plugin 构建规则,这样生成的动态库已经以静态链接的方式包含了其依赖。
-
cc_binary: 定义C++可执行文件目标
以下两个详细信息应该参照官方文档,这里仅作简单补充 文档地址
- proto_library : 用于定义 protobuf 目标 deps 为 import 所涉及的其他 proto_library 自动依赖 protobuf 运行库,使用者不需要再显式指定。
- thrift_library: 用于定义thrift库目标 deps 为import所涉及的其他thrift_library 自动依赖thrift,使用者不需要再显式指定。 构建时自动调用thrift命令生成cpp和h,并且编译成对应的cc_library
4. Blade命令行
基本语法
blade <subcommand> [options...] [targets...]
subcommand :
- build 构建指定的目标
- test 构建并且运行指定的测试
- clean 清除指定目标的构建结果
- dump 输出一些内部信息
- query 查询目标的依赖项与被依赖项
- run 构建并运行单个可执行的目标
targets: 对应BUILD中的name,支持单选和多选目标,语法如下:
- path:name 表示 path 中的某个 target,这种形式称为直接目标。
- path:* 表示 path 中所有目标,但不包含其子目录
- path 是 path:* 的简写形式
- path/… 表示path中所有目标,并递归包括所有子目录 (Blade 会递归搜索
BUILD
文件,如果需要排除某些目录,在其中放一个空的.bladeskip
文件即可) - :name 表示当前目录下的某个目标
如果 path 以 // 开始,则表示从工作空间的根目录开始。name 部分不是通配符的称为“直接目标”。
如果没有指定目标模式,则默认为当前目录下的所有目标(不包含子目录),如果当前目录下没有 BUILD 文件,就会失败。 当指定 … 作为结尾目标时,如果其路径存在,即使展开为空,也总不会失败。
注意:
如果你安装了 ohmyzsh,裸的 … 会被其自动展开为 …,需要写成 ./…