Bazel-Apollo编译工具

Bazel介绍

Bazel是一个类似于Make的编译工具,是Google为其内部软件开发的特点量身定制的工具,现在Google内部大部分软件都用Bazel进行构建。

安装

安装过程参考:http://bazel.io/docs/install.html

主要特点

  • 多语言支持:Bazel支持Java,Objective-C和C++,可以扩展来支持任意的编程语言
  • 高级别的构建语言:工程是通过BUILD语言来描述的。BUILD语言以简洁的文本格式,描述了由多个小的互相关联的库、二进制程序和测试程序来组成的一个项目。而与之相比,Make这类的工具需要描述各个单独的文件和编译的命令
  • 多平台支持:同一套工具和同样的BUILD文件可以用来构建不同架构和不同平台的软件
  • 可重现性:在BUILD文件中,每个库,测试程序,二进制文件必须明确完整地指定直接依赖。当修改源代码文件后,Bazel使用这个依赖信息就可以知道哪些必须重新构建,哪些任务可以并行执行。这意味者所有的构建都是增量形式的并能够每次都生成相同的结果
  • 可扩展性:Bazel可以处理巨大的构建,并且可以扩展支持新语言和新平台
  • 编译迅速:Bazel可以成倍提高构建速度,对依赖关系进行优化,支持并发执行;只重新编译修改的文件
  • 可伸缩性:可处理任意大小的代码库,并且库可以任意增加或缩减

适用项目:

  • 有庞大代码库的项目
  • 用多种语言写的项目
  • 在多平台上部署的项目
  • 有大量测试的工程

项目结构

Bazel工程的顶层为根目录,即工作区间Workspace;Workspace下包含多个Package,而每个Package又包含多个编译目标Target。

  • Workspace
    Bazel的编译是基于Workspace的概念,工作区存放了所有源代码和Bazel编译输出的文件。
    Bazel工程的根目录下,必须包含一个文件名为Workspace的文件,它用来指明构建的根目录,Workspace采用类似于Python的语法。
  • Package
    Workspace下可以有多个Package,每个Package都可以作为一个子模块,并且每个Package下必须包含一个BUILD文件,用来指定Package编译构建的规则。
  • Target
    Target是Bazel的最小组成

Bazel编译Demo

Bazel提供了一些编译的例子,在Github上可以找到:https://github.com/bazelbuild/examples/。现选取其中的examples/cpp-tutorial进行演示:

examples
└── cpp-tutorial
    ├──stage1
    │  └── main
    │      ├── BUILD
    │      ├── hello-world.cc
    │  └── WORKSPACE
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   ├── hello-greet.h
    │  └── WORKSPACE
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── WORKSPACE

example一共分成了三组文件,分别对应以下列举的三个例子。第一个例子:学习如何构建单个Package中的单个Target;第二个例子:将整个项目拆分成单个Package的多个Target。第三个例子:将项目拆分成多个Package,用多个Target编译。

理解BUILD文件

一个BUILD文件包含多种指令。其中最重要的是编译指令,它告诉Bazel如何编译想要的输出,比如可执行二进制文件或库。BUILD文件中的每一条编译指令被称为一个Target,它指向一系列的源文件和依赖,一个Target也可以指向别的Target。

举个简单例子:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

一个名为“hello-world”的Target使用了Bazel内置的cc_binary编译指令,从hello-world.cc源文件(没有其他依赖)构建了一个可执行二进制文件。

第一个例子

首先通过下面的命令来编译该例子:

bazel build //main:hello-world

Target中的//main:是BUILD文件相对于WORKSPACE文件的位置,hello-world是我们在BUILD文件中命名号的Target名字。

输出以下信息表示编译成功:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

Bazal将编译的输出放在项目根目录下bazel-bin目录.

查看依赖图

成功地构建一个项目,需要将项目所有的依赖都定义在BUILD文件内,Bazel可以通过命令生成可视化关系图,来检查这些依赖项.
以下是检查该项目的依赖命令:

bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph

这个命令意在查找Target//main:hello-world的所有依赖项(不包括host和隐式依赖),输出结果为输出图的文字描述,通过粘贴文字描述到GraphViz,就可以查看到如下的依赖图:

第二个例子

单个Target编译对于小项目来说非常高效,但是对于大项目而言,将项目拆分成多个Target和多个Package更加高效,因为Bazel能够实现快速增量的编译.
第二个例子将项目拆分成两个Target,cpp-tutorial/stage2/main目录下的BUILD文件如下所示:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

从BUILD文件可以看出Bazel首先编译了hello-greet这个库(利用Bazel内置的cc_library编译指令),然后编译hello-world这个二进制文件。hello-world这个Target的deps属性告诉Bazel,要构建hello-world这个二进制文件需要hello-greet这个库。

接下来再来看看项目生成的依赖图:
在这里插入图片描述

hello-world在编译时候的结构和例子1有所不同,现在是有两个Targets。hello-world这个Target从一个源文件编译而来,同时依赖于另一个Target//main:hello-greet,这个Target又是从两个源文件编译而来。

第三个例子

第三个例子将项目拆分成多个Package,cpp-tutorial/stage3目录如下:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── WORKSPACE

注意到我们现在有两个子目录了,每个子目录中都包含了BUILD文件。因此,对于Bazel来说,整个工作区现在就包含了两个Package:libmain,两个Package的BUILD文件如下:
lib/BUILD:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

注意到这个Target显式可见了(通过visibility属性)。这是因为默认情况下,Targets只对同一个BUILD文件里的其他Targets可见(Bazel使用Target visibility来防止像公有API中库的实现细节的泄露等情况)。

main/BUILD:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

可以看出hello-world这个main Package中的Target依赖于lib Package中的hello-time Target(即Target label为://lib:hello-time).那么现在依赖图就变成了下图的样子:

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值