• 一文学会使用Bazel构建C++项目


    什么是 Bazel?

    简介

    Bazel 是一个开源构建和测试工具,类似于 Make、Maven 和 Gradle。它使用人类可读的简要构建语言。Bazel 支持多种语言的项目,并针对多个平台构建输出。Bazel 支持跨多个代码库和大量用户的大型代码库。


    特点

    • 高级构建语言:使用人类可读的抽象语言在高度语义上描述项目的构建属性。与其他工具不同,Bazel 会根据库、二进制文件、脚本和数据集的概念进行操作,避免了编写对编译器和连接器等工具进行单独调用的复杂性。
    • 快速而又可靠:借助高级的本地和分布式缓存来记录所有已经完成的工作,并跟踪对文件内容和构建命令所做的更改。这样,Bazel 就能知道何时需要重新构建某些内容,并且仅重建必要的内容。如需进一步加快构建速度,您可以将项目设置为以高度并行且增量的方式进行构建。
    • 跨平台:可以在 Linux、macOS 和 Windows 上运行。Bazel 可以在同一个项目中为多个平台(包括桌面端、服务器和移动端)构建二进制文件和可部署软件包。
    • 可伸缩:在处理具有 10 万多个源文件的 build 时,Bazel 也能够保持敏捷性。Bazel 还可以帮助你扩展你的组织、代码库和持续集成系统,它可与数以万计的代码库和用户群进行协作。
    • 可拓展:支持如 C++、Java、Android、iOS、Go和其他各种语言平台,并且可以使用 Bazel 熟悉的扩展语言轻松添加对新语言和平台的支持,分享和重用由不断增长的 Bazel 社区编写的语言规则。


    使用流程

    如果需要使用 Bazel 构建或测试项目,通常需要执行以下操作:

    1. 设置 Bazel。下载并安装 Bazel。
    2. 设置项目的 WORKSPACE。这是 Bazel 用于查找构建输入、BUILD 文件的目录,以及存储构建输出的目录。
    3. 编写 BUILD 文件。告诉 Bazel 应该构建什么以及如何去构建。
    4. 通过命令行运行 Bazel。运行后 Bazel 会将结果输出至 WORKSPACE 中。


    构建流程

    当运行构建或测试时,Bazel 会执行以下操作:

    1. 加载与目标相关的 BUILD 文件。
    2. 分析输入及其依赖项,应用指定的构建规则,并生成操作图。
    3. 对输入执行构建操作,直到生成最终构建输出。


    基本概念

    WORKSPACE(工作区)

    工作区是一个目录,它包含了构建目标所需要的源码文件。每个工作区中都有一个名为 WORKSPACE 的文本文件,该文件可能为空,也可能包含构建输出所需的外部依赖的引用。包含名为 WORKSPACE 的文件的目录被视为工作区的根。

    代码以代码库形式组织。包含 WORKSPACE 文件的目录是主代码库的根目录,也称为 @。其他(外部)代码库是使用工作区规则在 WORKSPACE 文件中定义的。


    PACKAGE(包)

    包是工作区中主要的代码组织单元,其中包含一系列相关的文以及描述这些文件之间关系的 BUILD 文件。

    包是工作区的子目录,它的根目录必须包含文件 BUILD(或 BUILD.bazel)。除了那些具有 BUILD 文件的子包以外,其它子目录属于包的一部分。


    TARGET(目标)

    包是目标的容器,所以目标在包的 BUILD 文件中定义。 目标主要分为以下几种类型:

    • File(文件)

      • 源文件(Source File):由相关人员编写的,并签入到代码库中。
      • 派生文件(Derived files):又叫输出文件,是构建工具依据规则自动生成的文件。
    • Rule(规则):每个规则实例均指定一组输入文件和输出文件之间的关系。 规则的输入可以是源文件,但也可以是其他规则的输出。

      • 在BUILD中声明规则的语法时:

        规则类型(
            name = "...",  #必须
            其它属性 = ...	#可选
        )
        
        • 1
        • 2
        • 3
        • 4
      • 规则的类型一般以编程语言为前缀,例如 cc,java,后缀通常有:

        • *_binary:用于构建目标语言的可执行文件

        • *_test:用于自动化测试,其目标是可执行文件,如果测试通过应该返回零。

        • *_library:用于构建目标语言的库

    • PACKAGE GROUP(包组):即一组包,主要用于限制某些特定规则的可见性。由 package_group 函数定义。它们有三个属性:它们包含的包列表、名称以及它们包含的其他包组。引用它们的唯一方法来自规则的 visibility 属性或 package 函数的 default_visibility 属性,且它们不会生成或使用文件。

    规则生成的文件与规则本身始终属于同一个包,即无法将文件生成到其他包中。但是规则的输入可以来自其他包。


    LABEL(标签)

    目标的名称又被称为标签。每个标签都唯一标识一个目标。规范格式的典型标签如下所示:

    @myrepo//my/app/main:app_binary
    
    • 1

    以上面的标签为例,它由三部分组成

    • 代码库名:标签的第一部分是代码库名称,例如上面的 @myrepo//。如果标签引用了其使用的同一个代码库,其代码库标识符可以缩写为 //,例如:

      //my/app/main:app_binary
      
      • 1
    • 包名:标签的第二部分是包名称 my/app/main,这是相对于代码库根目录的包路径。如果标签引用了其使用的同一个包,则可以省略包名称(还可以选择是否使用英文冒号进行分隔),例如:

      #两者等价
      app_binary
      :app_binary
      
      • 1
      • 2
      • 3
    • 目标名:标签后面的冒号部分 app_binary 是目标名称。当与软件包路径的最后一个组成部分匹配时,可以省略冒号和冒号,例如:

      #两者等价
      //my/app/lib
      //my/app/lib:lib
      
      • 1
      • 2
      • 3

      在 BUILD 文件中,引用当前包中定义的规则时,冒号不能省略。而引用当前包中文件时,冒号可以省略。 但是,从其它包引用时、从命令行引用时,都必须使用完整的标签。


    BUILD(构建文件)

    每个包中都包含一个 BUILD 文件,而 BUILD 文件中的构建规则的每个实例又称为目标,并指向一组特定的源文件和依赖项。 目标还可以指向其他目标。

    BUILD 文件包含几种不同类型的 Bazel 指令。最重要的类型是构建规则,告知 Bazel 如何构建所需的输出,例如可执行二进制文件或库。BUILD 文件中的语句会被从上而下的逐条解释,某些语句的顺序很重要, 例如变量必须先定义后使用,但是规则声明的顺序无所谓。

    为了促进代码和数据之间的完全分离,BUILD 文件不能包含函数定义、for 语句或 if 语句,但可以在 .bzl 文件中声明函数、控制结构,并在BUILD文件中用 load 语句加载。例如:

    load("//foo/bar:file.bzl", "some_library")
    
    • 1

    上面的语句加载 foo/bar/file.bzl 并添加其中定义的符号 some_libraray 到当前环境中,load 语句可以用来加载规则、函数、常量(字符串、列表等)。(参数必须是字符串字面量(无变量),并且 load 语句必须出现在顶层,它们不能位于函数正文中。


    DEPENDENCY(依赖项)

    如果在构建或执行时 A 需要目标 B,目标 A 会依赖于目标 B。依存关系会使目标产生有向无环图(DAG),这就是所谓的依赖图。

    直接依赖项是指依赖项图表中长度为 1 的路径可以访问的其他目标。 传递依赖项是指它通过图中任意长度的路径来依赖的那些目标。

    大多数构建规则都具有三个用于指定不同类型通用依赖项的属性:srcs、deps 和 data。

    • srcs:直接输出源文件的规则所使用的文件。
    • deps:指向独立编译的模块的规则,这些模块提供头文件、符号、库、数据等。
    • data:构建目标可能需要一些数据文件才能正常运行。这些数据文件不是源代码:它们不会影响目标的构建方式。


    实战

    环境搭建

    安装 Bazel

    这里以 CentOS 8 为例:

    1. 首先从 Fedora COPR 下载相应的 .repo 文件并将其复制到 /etc/yum.repos.d/ 中:

      wget --no-check-certificate -P /etc/yum.repos.d/ https://copr.fedorainfracloud.org/coprs/vbatts/bazel/repo/epel-7/vbatts-bazel-epel-7.repo
      
      • 1
    2. 使用 yum 安装 Bazel:

      yum -y install bazel4
      
      • 1
    3. 查看 Bazel 版本号,看看是否安装成功:

      bazel --version
      
      • 1


    安装示例项目

    从 Bazel 的 GitHub 中下载官方提供的示例项目:

    git clone https://github.com/bazelbuild/examples
    
    • 1

    示例项目位于 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    示例主要分为三个阶段:

    • stage1:构建位于单个软件包中的单个目标。
    • stage2:将项目拆分为多个目标,但将其保留在单个软件包中。
    • stage3:将项目拆分为多个软件包,并使用多个目标进行构建。


    C++ 项目构建示例

    基础知识

    构建项目

    想要构建项目,只需要在命令行输入 bazel build 命令,并在其后面指定标签名,例如:

    bazel build 标签名
    
    • 1

    当构建执行成功时,就会在终端输出提示信息,其中包括每个目标构建输出的位置、构建时间、关键路径等,具体的输出信息可以参考下面的几个示例。

    当构建完成后,可以使用 bazel clean 命令清除构建结果。


    查看依赖项图表

    成功的构建会将其所有依赖项明确列在 BUILD 文件中。Bazel 会使用这些语句创建项目的依赖项图,以实现准确的增量构建。

    如需直观呈现示例项目的依赖项,您可以通过在工作区根目录下运行以下命令,生成依赖项图的文本表示形式:

    bazel query --notool_deps --noimplicit_deps "依赖项属性(目标)" \
      --output graph
    
    • 1
    • 2

    上述命令指示 Bazel 查找目标的所有依赖项(不包括主机和隐式依赖项),并将输出格式化为图表。接着将文本粘贴到 WebGraphviz 中即可查看。


    示例一:编译单个目标

    首先我们需要查看示例一的 BUILD 文件,规则可参考该链接 cc_library 规则。文件内容如下:

    load("@rules_cc//cc:defs.bzl", "cc_binary")
    
    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,依赖关系非常简单,只需要编译一个 TARGET,并且这个 TARGET 也只依赖于 hello-world.cc。直接运行构建命令:

    bazel build //main:hello-world
    
    • 1

    此时 Bazel 生成以下输出信息:

    Starting local Bazel server and connecting to it...
    INFO: Analyzed target //main:hello-world (15 packages loaded, 57 targets configured).
    INFO: Found 1 target...
    Target //main:hello-world up-to-date:
      bazel-bin/main/hello-world
    INFO: Elapsed time: 14.070s, Critical Path: 1.19s
    INFO: 6 processes: 4 internal, 2 processwrapper-sandbox.
    INFO: Build completed successfully, 6 total actions
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行生成的二进制文件,看看是否能够正常运行:

    bazel-bin/main/hello-world
    
    #输出
    Hello world
    Sun Jun  5 18:14:07 2022
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着生成示例项目的依赖项图表:

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

    此时输出依赖项文本如下:

    digraph mygraph {
      node [shape=box];
      "//main:hello-world"
      "//main:hello-world" -> "//main:hello-world.cc"
      "//main:hello-world.cc"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    将其复制到 WebGraphviz 中,得到的依赖项图表如下:

    hello-world 的依赖关系图会显示带有单个源文件的单个目标。


    如上图,示例一仅具有一个目标,该目标会构建没有其他依赖项的单个源文件。


    示例二:编译多个目标

    接着我们查看示例二的 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",
        ],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这个 BUILD 文件中存在两个 TARGET,分别是库文件 hello-greet 与 可执行文件 hello-world。由于 hello-world 目标中的 deps 属性说明该目标的构建依赖于 hello-greet,因此在构建时会先构建 hello-greet 库,再根据这个库构建 hello-world。

    执行下面的命令开始构建:

    bazel build //main:hello-world
    
    • 1

    输出结果如下:

    Starting local Bazel server and connecting to it...
    INFO: Analyzed target //main:hello-world (15 packages loaded, 60 targets configured).
    INFO: Found 1 target...
    Target //main:hello-world up-to-date:
      bazel-bin/main/hello-world
    INFO: Elapsed time: 11.865s, Critical Path: 0.25s
    INFO: 7 processes: 4 internal, 3 processwrapper-sandbox.
    INFO: Build completed successfully, 7 total actions
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行生成的二进制文件,看看是否能够正常运行:

    bazel-bin/main/hello-world
    
    #输出
    Hello world
    Sun Jun  5 20:01:37 2022
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着生成示例项目的依赖项图表:

    hello-world 的依赖关系图显示了对文件进行修改后的结构变化。



    如上图,示例二构建包含两个目标的项目。hello-world 目标会构建一个源文件,并依赖于另一个目标 hello-greet,后者会构建两个额外的源文件。


    示例三:编译多个包

    由于示例三中需要对多个包进行编译,所以我们首先使用 tree 命令来看看它们的目录结构:

    .
    ├── lib
    │   ├── BUILD
    │   ├── hello-time.cc
    │   └── hello-time.h
    ├── main
    │   ├── BUILD
    │   ├── hello-greet.cc
    │   ├── hello-greet.h
    │   └── hello-world.cc
    ├── README.md
    └── WORKSPACE
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,在工作区中包含了两个子目录 lib 和 main,并且这两个子目录中都有 BUILD 文件,因此它们都是包。

    接着查看这两个包中的 BUILD 文件:

    #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/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",
        ],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    我们发现,这两个包中存在跨包依赖。main 包中的 hello-world 不仅依赖于本包的 hello-greet,还依赖于 lib 包中的 hello-time。因此我们需要确保 lib 包中的 hello-time 对 main 包中的 hello-world 可见(默认情况下仅对同一 BUILD 文件中的其他目标可见,即可见性为 //visibility:private),这时就需要在 hello-time 目标中加上 visibility 属性,并将可见性设置为 //main:__pkg__,即对 main 包可见。

    执行下面的命令开始构建:

    bazel build //main:hello-world
    
    • 1

    输出结果如下:

    Starting local Bazel server and connecting to it...
    INFO: Analyzed target //main:hello-world (16 packages loaded, 63 targets configured).
    INFO: Found 1 target...
    Target //main:hello-world up-to-date:
      bazel-bin/main/hello-world
    INFO: Elapsed time: 8.273s, Critical Path: 0.26s
    INFO: 8 processes: 4 internal, 4 processwrapper-sandbox.
    INFO: Build completed successfully, 8 total actions
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行生成的二进制文件,看看是否能够正常运行:

    bazel-bin/main/hello-world
    
    #输出
    Hello world
    Sun Jun  5 20:26:44 2022
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着生成示例项目的依赖项图表:

    hello-world 的依赖关系图显示了 main 包中的目标如何依赖于 lib 软件包中的目标。


    C++ 常见构建方法

    在目标中包含多个文件

    Glob 是一个辅助函数,可用于查找符合特定路径模式的所有文件,并返回一个包含可变排序的新路径列表。Glob 仅在自己的包中搜索文件,并且仅查找源文件(而不是生成的文件或其他目标)。

    如果想在单个目标中包含多个文件,可以使用 glob 函数来进行文件匹配,例如:

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

    使用此目标时,Bazel 将在其找到的包含此目标的 BUILD 文件所在的目录(不包括子目录)中构建找到的所有 .cc 和 .h 文件。


    使用传递性包含

    如果文件包含一个头文件,那么任何以该文件为源的规则(即在 srcs、hdrs 或 textual_hdrs 属性中有该文件)都应该依赖于所包含的头文件的库规则。反过来说,只有直接依赖项才需要指定为依赖项。

    假设 sandwich.h 包含 bread.h,bread.h 包含 flour.h。sandwich.h 不包括 flour.h,因此 BUILD 文件将如下所示:

    cc_library(
        name = "sandwich",
        srcs = ["sandwich.cc"],
        hdrs = ["sandwich.h"],
        deps = [":bread"],
    )
    
    cc_library(
        name = "bread",
        srcs = ["bread.cc"],
        hdrs = ["bread.h"],
        deps = [":flour"],
    )
    
    cc_library(
        name = "flour",
        srcs = ["flour.cc"],
        hdrs = ["flour.h"],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    此时 sandwich 库依赖于 bread 库,又 bread 依赖于 flour 库。


    添加包含路径

    如果不想在工作区的根目录中设置包含(include)路径。而现有的库可能已经有了一个 include 目录,但是它却与工作区中的路径不一致,例如下面目录结构:

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

    Bazel 应该包含 some_lib.h 作为 legacy/some_lib/include/some_lib.h,但假设 some_lib.cc 包含 some_lib.h。要使该包含路径有效,legacy/some_lib/BUILD 需要在 copts 属性中指定 some_lib/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

    这对于外部依赖项尤其有用,因为其头文件必须包含 / 前缀。


    包含外部库

    gtest(Google Test)是一个跨平台的(Liunx、Mac OS X、Windows等)C++单元测试框架,由 Google发布。gtest 是为在不同平台上为编写 C++ 测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、死亡测试等等。

    当我们需要引入一个外部库时,可以使用 WORKSPACE 文件中的某个代码库函数下载,并代码库中提供它,这里以 gtest 为例:

    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    http_archive(
        name = "gtest",
        url = "https://github.com/google/googletest/archive/release-1.10.0.zip",
        sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91",
        build_file = "@//:gtest.BUILD",
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接着创建 gtest 的 BUILD 文件,由于 gtest 具有几项特殊要求,使得其 cc_library 规则更加复杂:

    • googletest-release-1.10.0/src/gtest-all.cc #includegoogletest-release-1.10.0/src/ 中的所有其他文件执行排除操作:将其从编译中排除,以防止出现重复符号的链接错误。
    • 它使用相对于 googletest-release-1.10.0/include/ 目录 (gtest/gtest.h) 的头文件,因此必须将该目录添加到包含路径。
    • 它需要关联 pthread,因此需要添加为 linkopt。

    规则将如下所示:

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

    由于所有内容都以 googletest-release-1.10.0 为前缀,是归档结构的副产物。可以通过添加 strip_prefix 属性让 http_archive 去除此前缀:

    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    http_archive(
        name = "gtest",
        url = "https://github.com/google/googletest/archive/release-1.10.0.zip",
        sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91",
        build_file = "@//:gtest.BUILD",
        strip_prefix = "googletest-release-1.10.0",
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时 gtest.BUILD 可以简化为:

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

    编写一个使用 gtest 的测试代码,看看是否能够成功构建:

    #include "gtest/gtest.h"
    #include "main/hello-greet.h"
    
    TEST(HelloTest, GetGreet) {
      EXPECT_EQ(get_greet("Bazel"), "Hello Bazel");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建测试文件的 BUILD:

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

    使用 bazel test 进行测试:

    bazel test test:hello-test
    
    #输出
    INFO: Found 1 test target...
    Target //test:hello-test up-to-date:
      bazel-bin/test/hello-test
    INFO: Elapsed time: 4.497s, Critical Path: 2.53s
    //test:hello-test PASSED in 0.3s
    
    Executed 1 out of 1 tests: 1 test passes.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10


    添加对预编译库的依赖关系

    如果想使用只包含已编译版本的库(例如头文件和 .so 文件),就需要将其封装在 cc_library 规则中,例如:

    cc_library(
        name = "mylib",
        srcs = ["mylib.so"],
        hdrs = ["mylib.h"],
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    网络接口登录方法获取cookie
    SpringBoot启动流程源码分析
    精心总结十三条建议,帮你创建更合适的MySQL索引
    win10 + chrome 死机
    【毕业设计】基于stm32的WiFi监控小车 - 物联网 单片机 嵌入式
    基于麻雀搜索算法的PID神经网络解耦控制算法研究附Matlab代码
    MySQL实训--原神数据库
    1141 PAT Ranking of Institutions
    告诉你如果对一个新产品进行测试
    虚拟人动作捕捉会成为虚拟人三维动画宣传片的主流吗?
  • 原文地址:https://blog.csdn.net/qq_35423154/article/details/125590017