Blade是腾讯为了解决GNU Make使用繁琐的问题而开发的一个开源构建工具,旨在简化大型项目的构建,能够自动分析依赖,集成了编译、链接、测试、静态代码检查等功能,支持C/C++, Java, Python, Scala, protobuf等多种语言(主要面向C/C++)。
注意:构建(build)和编译(compile)不同,编译器负责将源代码转换为库文件或可执行文件,构建工具负责分析库之间的依赖关系,并调用编译器来生成构建目标。
例如,自己的代码依赖A库,A库又依赖B库,如果手动编译则需要写复杂的编译和链接命令,当依赖库代码发生变化时还需要重新编译,构建工具旨在自动化这一过程。
项目主页:https://github.com/chen3feng/blade-build
官方文档:https://github.com/chen3feng/blade-build/blob/master/doc/en/README.md
用户手册:https://github.com/chen3feng/blade-build/blob/master/doc/blade_user_manual.pdf
介绍ppt:https://github.com/chen3feng/blade-build/blob/master/doc/blade.pdf
1.特性
- 自动分析库之间的依赖关系
- 递归构建:当依赖库的源文件发生变化时会自动重新构建依赖库,而GNU Make无法实现递归构建
- 增量构建:未发生变化的依赖库不会重新构建,加快构建速度
- 提供了对protobuf和测试(使用gtest)的内置支持
2.依赖软件
Blade需要以下依赖:
- Linux或Mac操作系统
- Python 2.7
- Ninja 1.8+
构建特定语言所需的编译器:
- C/C++: gcc 4.0+
- Java: JDK 1.6+
- Scala: 2.10+
3.安装
3.1 安装Python
Linux或Mac系统默认已经安装了Python 2.7。
3.2 安装Ninja
下载地址:https://github.com/ninja-build/ninja/releases
解压后只有一个可执行文件ninja,将其放到PATH环境变量包含的某个目录下(例如/usr/local/bin),从而能够直接在命令行中直接执行ninja命令:
$ ninja --version
1.10.2
3.3 安装Blade
安装方式:下载源代码,执行install脚本
$ git clone https://github.com/chen3feng/blade-build.git
$ cd blade-build/
$ git checkout v2.0
$ ./install
执行完成后Blade将被安装在~/bin目录下,该目录也被添加到PATH环境变量,执行source ~/.profile
命令或重启终端使其生效,此时应该能够在命令行中直接执行blade命令:
$ blade -h
usage: blade [-h] [--version] {build,run,test,clean,query,dump} ...
blade <subcommand> [options...] [targets...]
...
注意:不能删除blade-build目录,因为blade
命令会用到其中的源代码。
4.简单示例
下面使用Blade创建一个hello-world项目。
官方文档:https://github.com/chen3feng/blade-build/blob/master/doc/en/quick_start.md
4.1 创建工作目录
$ mkdir hello-world
$ cd hello-world/
$ touch BLADE_ROOT
其中项目根目录hello-world可以在任意位置,BLADE_ROOT文件用于标识项目根目录。
4.2 实现say库
在hello-world目录下创建头文件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";
}
这两个文件组成了一个库(library),可以将其编译为库文件供其他代码使用,创建一个BUILD文件来描述say库:
cc_library(
name = 'say',
hdrs = ['say.h'],
srcs = ['say.cpp'],
)
其中cc_library
表示这是一个C/C++库,hdrs
表示该库的公共接口头文件,srcs
表示该库的源文件。
4.3 实现hello库
下面创建另一个库hello,并调用say库提供的函数。
创建头文件hello.h:
#pragma once
#include <string>
// Say hello to `to`
void Hello(const std::string& to);
创建源文件hello.cpp:
#include "hello.h"
#include "say.h"
void Hello(const std::string& to) {
Say("Hello, " + to);
}
其中函数Hello()
的实现调用了say库提供的函数Say()
,因此hello库依赖say库。
在BUILD文件中添加hello库的定义:
cc_library(
name = 'hello',
hdrs = ['hello.h'],
srcs = ['hello.cpp'],
deps = [':say'],
)
其中deps
表示该构建目标的依赖,:say
表示当前BUILD文件中名为say的目标,即前面的say库。
4.4 实现hello-world程序
下面创建一个hello-world程序,在main()
函数中调用hello库提供的函数来打印信息。
创建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
,表示该构建目标的产出是一个可执行程序。
注意:依赖只需要添加hello,而不需要添加say,因为这是hello库的实现细节,在编译和链接过程中Blade会自动处理这样的传递依赖。但如果hello_world.cpp中显式包含了say.h,则依赖中需要添加say。
下面构建并运行hello-world程序:
$ blade build :hello-world
$ blade run :hello-world
Hello, World!
注:如果安装的是最新版本的Blade,这里会报错Blade(error): Target “//.:hello-world” does not exist,解决方法是切换到v2.0版本重新安装。
blade build
命令底层了调用g++编译器和ld链接器,生成的库文件和可执行文件在build64_release目录下。blade run
命令就是运行生成的可执行文件build64_release/hello-world,可使用--verbose
参数查看具体执行的命令。
注:对于这个简单的示例,直接执行
g++ -o hello_world hello_world.cpp hello.cpp say.cpp
即可完成编译,但是对于具有上百个源文件的大型项目,使用构建工具就很有必要了。
完整的项目目录结构如下:
hello-world/
BLADE_ROOT
BUILD
hello_world.cpp
hello.cpp
hello.h
say.cpp
say.h
注:该示例将所有头文件和源文件都放在项目根目录下,实际的项目中会按模块将文件分为不同的子目录,每个子目录下都包含一个BUILD文件。
完整代码:https://github.com/chen3feng/blade-build/blob/master/example/quick-start
example目录下也有其他示例
5.代码组织结构
Blade要求项目有一个显式的根目录,即BLADE_ROOT文件所在目录,根目录下是自己的模块子目录和第三方库目录,每个子目录下都有一个BUILD文件来声明该模块所包含的一个或多个库。
以下是一个示例目录结构:
my-project/
BLADE_ROOT
common/
string/
BUILD
algorithm.h
algorithm.cpp
foo/
BUILD
foo.h
foo.cpp
thirdparty/
gtest/
BUILD
gtest.h
gtest.cc
...
(这是Google推荐的源代码管理方式:所有代码放在同一个仓库中,包括第三方库代码)
代码中#include
头文件的相对路径和BUILD文件中依赖的相对路径都是基于项目根目录的
例如:common/string/BUILD声明了一个string库,包括头文件algorithm.h,则在foo/foo.h中通过#include "common/string/algorithm.h"
包含该头文件,在foo/BUILD中通过'//common/string:string'
声明依赖
6.BUILD文件
Blade通过一系列名为BUILD(全部大写)的文件指定构建目标,每个构建目标由一些头文件和源文件组成,其他构建目标可以依赖这个目标并引用其头文件。
BUILD文件中只需要指定目标所包含的头文件、源文件和直接依赖,执行构建命令时Blade将自动分析依赖关系,并调用编译器和链接器来生成构建目标。
6.1 示例
假设common/string目录下定义了一些字符串辅助函数,并且依赖common/int目录下的int库,则common/string/BUILD文件如下:
cc_library(
name = 'string',
srcs = [
'algorithm.cpp',
'concat.cpp',
'format.cpp',
],
hdrs = [
'algorithm.h',
'concat.h',
'format.h',
],
deps = ['//common/int:int'],
)
其他库在依赖中通过'//common/string:string'
引用该库。
(BUILD文件可理解为Python函数调用语法)
(抄的Bazel?)
6.2 风格建议
- 缩进4个空格
- 使用单引号而不是双引号
- 目标名称使用小写
srcs
中的文件按字母顺序排列deps
先写当前目录下的依赖(:name
),再写其他目录下的依赖(//path/to/dir:name
),按字母顺序排列- 当每行一个参数时,最后一个参数也以逗号结尾,从而减少当增加或删除参数时影响的行数
- 不同目标之间空一行,每个目标前添加注释,注释以#开头
6.3 构建目标类型
Blade支持多种类型的构建目标,每种构建目标用于不同的语言、调用不同的编译器、产出不同的结果。以下是几种常用的构建目标的语法。
完整列表参考:https://github.com/chen3feng/blade-build/blob/master/doc/en/build_file.md#build-rules
6.3.1 cc_library
构建C/C++静态链接库,C文件(.c)使用gcc编译器,C++文件(.cpp、.cc等)使用g++编译器,使用ld链接器,生成静态链接库文件(.a)。
语法(仅包含了常用属性):
cc_library(
name ='foo',
hdrs = ['foo.h', 'bar.h', ...],
srcs = ['foo.cpp', 'bar.cpp', ...],
deps = [':name', '//path/to/dir:name', ...],
)
- name:构建目标的名称,和当前目录一起构成目标的唯一标识
- hdrs:公共接口头文件列表(私有头文件应声明在
srcs
中),如果只有一个则可省略中括号 - srcs:源文件列表,位于当前目录或当前目录的子目录下,如果只有一个则可省略中括号,可使用glob函数
- deps:依赖目标列表,支持以下格式:
//path/to/dir:name
:项目根目录下path/to/dir/BUILD文件中定义的名为name的目标(看到就知道它在哪):name
:当前BUILD文件中名为name的目标#name
:系统库
- visibility:对哪些目标可见(在Blade 2中,目标默认是私有的,即只对当前目录下的目标可见,可使用
visibility = ['PUBLIC']
指定对所有目标可见)
6.3.2 cc_binary
构建C/C++可执行文件(即包含main函数的程序)。
语法:
cc_binary(
name ='foo',
srcs = ['foo.cpp', 'bar.cpp', ...],
deps = [':name', '//path/to/dir:name', ...],
)
6.3.3 cc_test
C/C++单元测试,使用GoogleTest测试框架。
语法:
cc_test(
name = 'foo-test',
srcs = ['foo_test.cpp'],
deps = [':foo'],
testdata = ['test.txt'],
)
TODO 具体用法链接GoogleTest博客
6.3.4 proto_library
构建Protocol Buffers库,使用protoc编译器。
语法:
proto_library(
name = 'student_proto',
srcs = 'student.proto',
deps = ':base_proto',
)
7.命令行参考
官方文档:https://github.com/chen3feng/blade-build/blob/master/doc/en/command_line.md
Blade命令行语法:
blade <subcommand> [options...] [targets...]
7.1 子命令
- build:构建指定的目标
- run:构建并运行指定的目标
- test:构建指定的目标并运行测试
- clean:删除指定目标的构建产物
- query:分析指定的目标依赖或被依赖的目标
- dump:打印指定目标的内部信息
7.2 构建目标模式
子命令需要指定一个或多个构建目标参数,称为目标模式(target pattern),支持以下语法:
path:name
:path目录下名为name的目标:name
:当前目录下名为name的目标path:*
或path
:path目录下的所有目标,不包括子目录path/...
:path目录及其子目录下的所有目标
如果path以//开头则表示从项目根目录开始的路径,否则表示基于当前目录的相对路径。
如果没有指定目标则表示当前目录下的所有目标,不包括子目录。
7.3 示例
# 构建当前目录下的所有目标,不包括子目录
blade build
# 构建当前目录及其子目录下的所有目标
blade build ...
# 构建当前目录下名为hello的目标
blade build :hello
# 构建项目根目录/common/string目录下名为string的目标
blade build //common/string:string
# 构建当前目录/string目录下名为string的目标
blade build string:string
# 构建项目根目录/common及其子目录下的所有目标
blade build //common/...
8.测试
官方文档:https://github.com/chen3feng/blade-build/blob/master/doc/en/test.md
TODO 链接GoogleTest博客