Bazel学习记录

0.安装

# bazel-5.3.0-installer-linux-x86_64.sh
chmod +x bazel-version-installer-linux-x86_64.sh
./bazel-version-installer-linux-x86_64.sh

执行输出信息:

Bazel is now installed!

Make sure you have "/usr/local/bin" in your path.

For bash completion, add the following line to your ~/.bashrc:
  source /usr/local/lib/bazel/bin/bazel-complete.bash

For fish shell completion, link this file into your
/home/q/.config/fish/completions/ directory:
  ln -s /usr/local/lib/bazel/bin/bazel.fish /home/q/.config/fish/completions/bazel.fish

See http://bazel.build/docs/getting-started.html to start a new project!

根据提示,将 source /usr/local/lib/bazel/bin/bazel-complete.bash 添加到 ~/.bashrc 中:

echo 'source /usr/local/lib/bazel/bin/bazel-complete.bash' >> ~/.bashrc
source ~/.bashrc

测试安装:

bazel --version # bazel 5.3.0

接下来以官方给出的 demo进行学习,这个项目有 C++、java、android、java-maven等示例,进入 C++ 项目示例即可。

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

1.实例代码

stage1、stage2、stage3 是三个独立的示例工程项目(三个工程之间没有直接关系):

  • 示例 stage1 演示了一个简单的 C++ 程序
  • 示例 stage2 演示了一个 .cc 文件需要引用另一个 .cc 文件的情况
  • 示例 stage3 演示了一个 .cc 文件和 main 程序不在同一个目录下的情况(一个在 main 目录,一个在 lib 目录)

2.设置工作区

工作区是保存项目源文件和 Bazel 构建输出的目录。bazel相关的:

  • WORKSPACE : 工作空间,在项目工程的根(root)目录下,有个 WORKSPACE 文件,表示这是一个工程项目。比如,stage1 目录下有 WORKSPACE 文件,说明这个目录下有一个完整的工程项目
  • BUILD : BUILD 文件,这个文件会告诉 bazel 如何编译 .cc 源文件,限定多个源文件之间的关系。工作空间中的可能有多个文件夹,每个目录有 代码源文件 与 BUILD 文件,这些有 BUILD 文件的文件夹,被称为 包(pakege)
  • Bazel 编译构建一个项目(project)时,所有的输入文件和依赖文件,都应该要求在同一个工作空间(WORKSPACE)中。 虽然可以使用某种链接方式,达到使用其它工作空间的文件的目的,但一般不这么使用。

3.认识 BUILD 文件

BUILD 文件包含了一系列不同类型的指令,Bazel 工具执行这些指令,来构建和编译目标(target: 想生成的东西,如源代码生成的二进制可执行文件、.o中间文件、lib 库文件等等):

BUILD 文件中,最重要命令的就是 构建规则(build rule),它告诉 Bazel 如何去创建和生成我们的目标。比如 cpp-tutorial/stage1/main 目录下的 BUILD 文件,内容如下:

load("@rules_cc//cc:defs.bzl", "cc_binary")

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

上面规则中, hello-world 就是目标target, hello-world.cc 就是依赖文件,意思就是说,使用 hello-world.cc 文件,编译生成一个想要的名为 hello-world 的可执行文件。

4.stage1-单个目标,单个软件包

.
├── main
│   ├── BUILD
│   └── hello-world.cc
├── README.md
└── WORKSPACE

对stage-1进行编译: bazel build //main:hello-world

  • //main: 是相对路径,相对于工作区间 workspace 的根目录的路径

  • hello-world 是目标target

  • Bazel工具使用 main 目录下 BUILD 这个文件指定的编译规则,来编译生成目标. 编译过程,打印下面信息:

    Starting local Bazel server and connecting to it...
    INFO: Analyzed target //main:hello-world (37 packages loaded, 163 targets configured).
    INFO: Found 1 target...
    Target //main:hello-world up-to-date:
      bazel-bin/main/hello-world
    INFO: Elapsed time: 6.254s, Critical Path: 0.47s
    INFO: 6 processes: 4 internal, 2 linux-sandbox.
    INFO: Build completed successfully, 6 total actions
    

这时,项目空间的根目录下,生成目录 bazel-bin (生成了一些快捷方式,文件原位置在缓存中),这个目录中,包含有刚才编译生成的目标:

.
├── bazel-bin -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out/k8-fastbuild/bin
├── bazel-out -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out
├── bazel-stage1 -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__
├── bazel-testlogs -> /home/q/.cache/bazel/_bazel_q/e090bbc49e2071052269526408283032/execroot/__main__/bazel-out/k8-fastbuild/testlogs
├── main
│   ├── BUILD
│   └── hello-world.cc
├── README.md
└── WORKSPACE

执行编译生成的可执行文件 bazel-bin/main/hello-world

Hello world
Wed Oct 12 13:55:46 2022

5.查看依赖关系图

查看目标是依赖哪些文件生成,即可视化项目的依赖关系,可以通过在工作区根目录运行如下命令,来生成依赖关系图的文本表示:

bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" --output graph

执行结果如下:

digraph mygraph {
  node [shape=box];
  "//main:hello-world"
  "//main:hello-world" -> "//main:hello-world.cc"
  "//main:hello-world.cc"
}

上面的命令告诉 Bazel 查找目标 //main:hello-world 的所有依赖项(不包括主机和隐式依赖项)并将输出格式化为图形。然后,将文本粘贴到 GraphViz 中进行可视化。

此外,还可以通过安装 GraphViz 和 xdot Dot Viewer 在本地查看图形:

sudo apt update && sudo apt install graphviz xdot

然后,您可以通过将上面的文本输出直接传送到 xdot 来生成和查看图形:

xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" --output graph

6.stage2-构建多个目标

在stage1示例中,只有一个 .cc 源文件,但实际项目开发中,一般会有多个 .cc 源文件。如何编译多个 .cc 源文件这种情况。先进入示例 stage2 工程目录下.

.
├── main
│   ├── BUILD
│   ├── hello-greet.cc
│   ├── hello-greet.h
│   └── hello-world.cc
├── README.md
└── WORKSPACE

查看该项目下 main 文件夹下的 BUILD 文件,内容如下:

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")

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",
    ],
)
  • Bazel 会先编译 hello-greet 这个目标,这个目标是一个 C++ 的 lib 库文件(使用 bazel 内建规则 cc_library rule 来生成这个lib)
  • 然后,Bazel 再编译 hello-world 这个目标,这个目标是一个二进制可执行文件。deps 这个属性,告诉 Bazel 编译 hello-world 时,需要用到 hello-greet 这个目标,也就是 hello-world 的生成需要依赖(dependencies) hello-greet 。

执行命令来编译 stage2 这个工程:

bazel build //main:hello-world

编译完成以后,尝试执行一下生成的二进制文件:

bazel-bin/main/hello-world

打印结果如下:

Hello world
Wed Oct 12 14:13:25 2022

如果这时候,去修改 hello-greet.cc 这个源文件,那么,bazel 只会重新编译 ``hello-greet.cc` 这个文件,其它文件不会被重新编译。

请添加图片描述

请添加图片描述

查看一下依赖关系图:

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

请添加图片描述

7.stage3-多个软件包

可以将项目拆分为多个包,看一下cpp-tutorial/stage3目录的内容.

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

现在有两个子目录,每个子目录都包含一个 BUILD 文件。 因此,对于 Bazel,工作区现在包含两个包: lib 和 main

  • lib/BUILD 文件:

    load("@rules_cc//cc:defs.bzl", "cc_library")
    
    cc_library(
        name = "hello-time",
        srcs = ["hello-time.cc"],
        hdrs = ["hello-time.h"],
        visibility = ["//main:__pkg__"],  #对 main 包可见
    )
    
  • main/BUILD 文件:

    load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
    
    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",
        ],
    )
    
  • 未编译也可(通过 BUILD) 查看依赖关系:

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

请添加图片描述

  • 编译并执行

    bazel build //main:hello-world
    bazel-bin/main/hello-world
    

    输出:

    Hello world
    Wed Oct 12 14:35:26 2022
    

8.其他常用案例

  • 常见 C++ 构建用例

    • 在目标中包含多个文件
      cc_library(
          name = "build-all-the-files",
          srcs = glob(["*.cc"]),
          hdrs = glob(["*.h"]),
      )
      
    • 使用传递性包含
    • 添加包含路径
    • 包括外部库
    • 编写并运行 C++ 测试
    • 添加对预编译库的依赖关系
      cc_library(
        name = "mylib",
        srcs = ["mylib.so"],
        hdrs = ["mylib.h"],
      )
      
  • 规则查询

    • cc_binary
    • cc_import
    • cc_library
    • cc_proto_library
    • fdo_prefetch_hints
    • fdo_profile
    • propeller_optimize
    • cc_test
    • cc_toolchain
    • cc_toolchain_suite
  • 函数查询

    • package(default_deprecation, default_testonly, default_visibility, features)
    • package_group(name, packages, includes)
    • exports_files([label, …], visibility, licenses)
    • glob(include, exclude=[], exclude_directories=1, allow_empty=True)
    • select(
      {conditionA: valuesA, conditionB: valuesB, …},
      no_match_error = “custom message”
      )
    • subpackages(include, exclude=[], allow_empty=True)
  • 概览

  • bazel 命令行参考文档

  • 使用 Bazel 构建程序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值