Bazel基础

6/1/2021 cpp

# Why Bazel

看到一个回答腾讯微信事业群后台开发环境 (opens new window),社畜自觉学习一下……

# Install

sudo apt update && sudo apt install bazel
1

不过wsl里妹识别出来,手动装一下

Release地址 (opens new window)

sudo apt install g++ unzip zip
wget https://github.com/bazelbuild/bazel/releases/download/4.1.0/bazel-4.1.0-installer-linux-x86_64.sh
chmod +x bazel-4.1.0-installer-linux-x86_64.sh
./bazel-4.1.0-installer-linux-x86_64.sh --user
1
2
3
4

--user 选项会使得balze安装在$HOME/bin

然后将下面的命令写入配置文件

export PATH="$PATH:$HOME/bin"
1

# Building a C++ Project

参考项目 https://github.com/bazelbuild/examples

# 设置工作区

一份比较典型的bazel工作目录为

root
├── main
│   ├── BUILD
│   ├── hello-greet.cc
│   └── hello-greet.h
├── lib
│   ├── BUILD
│   ├── hello-time.cc
│   └── hello-time.h
└── WORKSPACE
1
2
3
4
5
6
7
8
9
10

在构建项目之前,需要先设置工作区(workspace),即你项目源代码的位置。

通常会包括:

  • 一个WORKSPACE在项目的根目录
  • 多个BUILD文件,告诉bazel怎么编译项目的不同部分。一个包含BUILD文件的目录会被Bazel识别为一个包(package)

# BUILD file

一份BUILD file会包含多种不同类型的指令。其中最重要的就是构建规则( build rule),告诉bazel如何进行BUILD

每个构建规则的实例被称为一个目标(target),包含一系列特定的源文件和依赖(也可以包含其他target)

一份最简单的BUILD文件,见cpp-tutorial/stage1/main

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)
1
2
3
4

这里包含了bazel内置的一个构建规则cc_binary rule (opens new window),告诉bazel如何从源文件构建一个可执行文件。

# Build

cpp-tutorial/stage1文件下执行

stage1
├── main
│   ├── BUILD
│   └── hello-world.cc
└── WORKSPACE
1
2
3
4
5
bazel build //main:hello-world
1

//main:BUILD文件根目录相对于WORKSPACE的位置

hello-world是我们在BUILD文件里指定的name字段

构建完成之后会在WORKSPACE所在的根目录下生成好几个文件夹

我们的可执行文件在bazel-bin/main/hello-world

# 构建多个目标

cpp-tutorial/stage2文件下执行

stage2
├── main
│   ├── BUILD
│   ├── hello-greet.cc
│   ├── hello-greet.h
│   └── hello-world.cc
└── WORKSPACE
1
2
3
4
5
6
7

hello-world.cc 引用了hello-greet.h

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",
    ],
)
1
2
3
4
5
6
7
8
9
10
11
12
13

bazel build //main:hello-world构建时,先构建hello-greetlibrary,然后构建hello-worldbinary

如果你只修改了helloworld.cc,bazel只会重新build hello-worldbinary的部分

# 使用多个包

cpp-tutorial/stage3文件下执行

stage3
|── main
│   ├── BUILD
│   ├── hello-greet.cc
│   └── hello-greet.h
├── lib
│   ├── BUILD
│   ├── hello-time.cc
│   └── hello-time.h
└── WORKSPACE
1
2
3
4
5
6
7
8
9
10

build 文件为

#lib/BUILD
cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)
#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",
    ],
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

需要注意的是默认情况下,target只对同一个build文件里的有效,所以我们需要显式指出对main下面的build可见,即visibility指令

# 常见场景示例

# 一个目标里包含多个文件

cc_library(
    name = "build-all-the-files",
    srcs = glob(["*.cc"]),
    hdrs = glob(["*.h"]),
)
1
2
3
4
5

使用glob函数 (opens new window)

# 添加引用路径

└── my-project
    ├── legacy
    │   └── some_lib
    │       ├── BUILD
    │       ├── include
    │       │   └── some_lib.h
    │       └── some_lib.cc
    └── WORKSPACE
1
2
3
4
5
6
7
8

假设你在somelib.cc写的时#include "somelib.h" ,但是bazel希望的方式是legacy/some_lib/include/some_lib.h ,为了使得路径可见,需要指定include 路径

cc_library(
    name = "some_lib",
    srcs = ["some_lib.cc"],
    hdrs = ["include/some_lib.h"],
    copts = ["-Ilegacy/some_lib/include"],
)
1
2
3
4
5
6

其实就是-Idir参数,应用外部文件的时候比较有用

copts 是为C++编译器提供的选项,在编译目标之前,这些选项按顺序添加到COPTS。这些选项仅仅影响当前目标的编译,而不影响其依赖。选项中的任何路径都相对于当前工作空间而非当前包。

# 测试和引用外部库

测试文件目录组织为

root
├── main
│   ├── BUILD
│   └── hello-world.cc
├── lib
│   ├── BUILD
│   ├── hello-greet.cc
│   └── hello-greet.h
├── test
│   ├── BUILD
│   └── hello-test.cc
├── BUILD       # necessary 为空即可,否则会报错
├── gtest.BUILD 
└── WORKSPACE
1
2
3
4
5
6
7
8
9
10
11
12
13
14

其中,test文件为

// test/hello-test.cc
#include "gtest/gtest.h"
#include "lib/hello-greet.h"

TEST(HelloTest, GetGreet) {
  EXPECT_EQ(get_greet("Bazel"), "Hello Bazel");
}
1
2
3
4
5
6
7

我们使用了gtest,所以需要先引入 gtest

首先在workspace 文件下载依赖

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "gtest",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    build_file = "@//:gtest.BUILD", # 如果项目已经包含BUILD文件,就不用自定义
)
1
2
3
4
5
6
7
8

然后创建gtest.BUILD,其实这点比较恶心,还得自己引入,c++好shi啊。

cc_library(
    name = "main",
    srcs = glob(
        ["googletest-release-1.7.0/src/*.cc"],
        exclude = ["googletest-release-1.7.0/src/gtest-all.cc"]
    ),
    hdrs = glob([
        "googletest-release-1.7.0/include/**/*.h",
        "googletest-release-1.7.0/src/*.h"
    ]),
    copts = [
        "-Iexternal/gtest/googletest-release-1.7.0/include",
        "-Iexternal/gtest/googletest-release-1.7.0"
    ],
    linkopts = ["-pthread"],
    visibility = ["//visibility:public"],
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

对应的./test/BUILD 文件未

cc_test(
    name = "hello-test",
    srcs = ["hello-test.cc"],
    copts = ["-Iexternal/gtest/googletest-release-1.7.0/include"],
    deps = [
        "@gtest//:main",
        "//lib:hello-greet",
    ],
)
1
2
3
4
5
6
7
8
9

运行bazel test test:hello-test

注意到这里有一串的googletest-release-1.7.0,其实可以省掉,需要在workspace里添加

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "gtest",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    build_file = "@//:gtest.BUILD",
    strip_prefix = "googletest-release-1.7.0", # key point
)
1
2
3
4
5
6
7
8
9

然后其他文件的googletest-release-1.7.0都可以删掉。

# 静态库和动态库

使用cc_import https://docs.bazel.build/versions/4.1.0/be/c-cpp.html#cc_import

# 总结

总的来说,bazel总的来说,脉络比cmake清晰很多,也简单很多,Makefile就别提了。

但是开源社区似乎更喜欢Cmake,因为Cmake更全面吗?

C++构建系统为什么这么麻烦啊,能不能爬

Last Updated: 6/1/2021, 10:48:42 AM