Bazel 快速入门与核心知识

news2024/9/21 11:09:45

Bazel 快速入门与核心知识

Bazel 简介

Bazel 是一款与 Make、Maven 和 Gradle 类似的开源构建和测试工具。 它使用人类可读的高级构建语言。Bazel 支持多种语言的项目 (C/C++, Java, Python, …),可为多个平台构建输出。Bazel 支持跨多个代码库和大量用户的大型代码库。

本文是作者结合 Bazel 官方文档以及一些其他博客总结的学习笔记,凝炼了个人认为最核心的一些 Bazel 知识。通过此文,希望能让大家不仅看懂并编译一个通过 Bazel 构建的项目,同时还能够使用 Bazel 对自己的项目完成构建。

使用 Bazel 的基本流程

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

  1. 设置 Bazel。下载并安装 Bazel。

  2. 设置项目工作区,这是 Bazel 在其中查找 build 输入和 BUILD 文件以及用于存储 build 输出的目录。

  3. 编写 BUILD 文件,告知 Bazel 要构建什么以及如何构建它。

    如需编写 BUILD 文件,您可以使用领域特定语言 Starlark 声明构建目标。(请查看此处的示例。)

    构建目标指定了一组 Bazel 将要构建的输入工件及其依赖项,Bazel 将用于构建它的构建规则,以及用于配置构建规则的选项。

    build 规则用于指定 Bazel 将使用的构建工具,例如编译器和链接器。Bazel 附带多条构建规则,这些规则涵盖受支持平台上以支持的语言显示的最常见工件类型。

  4. 通过命令行运行 Bazel。Bazel 会将您的输出内容放在工作区中。

Bazel 构建流程

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

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

由于之前的所有构建工作都已缓存,因此 Bazel 可以识别并重复使用缓存的 artifacts,并且只会重新构建或重新测试发生更改的内容。为了进一步强制执行正确性,您可以设置 Bazel,以通过沙盒化的方式运行构建和测试,从而最大限度地减少偏差并最大限度地提高可重现性。

Bazel C++ demo

目录组织结构如下。下面尝试用 bazel 构建 stage3/main 中以 hello-world.cc 为入口的 hello-world 程序,该程序依赖于同路径下的 hello-greet 以及 stage3/lib 路径下的 hello-time

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. stage3 目录下创建了名为 WORKSPACE 的空文件,标记了这是一个 bazel 的工作区

  2. stage3/mainstage3/lib 目录下创建名为 BUILD 的文件,用于指示 bazel 构建工作,一个拥有 BUILD 文件的目录就是一个包 (软件包)

    # lib/BUILD 文件
    
    # 定义了名为"hello-time"的一个目标(target),这个目标是cc_library规则(rule)的一个实例,cc_library规则定义的是构建C/C++库(library)的规则
    cc_library(
        name = "hello-time",
        # 构建此库目标所需的C/C++文件列表,路径相对于BUILD文件所处目录
        srcs = ["hello-time.cc"],
        # 描述此库目标的C/C++头文件列表,路径相对于BUILD文件所处目录
        hdrs = ["hello-time.h"],
        # 使用可见性属性让 lib/BUILD 中的 //lib:hello-time 目标对 main/BUILD 中的目标显式可见。这是因为,默认情况下,只有同一 BUILD 文件中的其他目标才会看到这些目标。
        visibility = ["//main:__pkg__"],
    )
    
    # main/BUILD 文件
    
    # 定义了名为"hello-greet"的一个目标(target),这个目标是cc_library规则(rule)的一个实例,cc_library规则定义的是构建C/C++库(library)的规则
    cc_library(
        name = "hello-greet",
        # 构建此库目标所需的C/C++文件列表,路径相对于BUILD文件所处目录
        srcs = ["hello-greet.cc"],
        # 描述此库目标的C/C++头文件列表,路径相对于BUILD文件所处目录
        hdrs = ["hello-greet.h"],
    )
    
    # 定义了名为"hello-world"的一个目标(target),这个目标是cc_binary规则(rule)的一个实例,cc_binary规则定义的是构建C/C++二进制程序(binary)的规则
    cc_binary(
        name = "hello-world",
        # 构建此二进制目标所需的C/C++文件列表,路径相对于BUILD文件所处目录
        srcs = ["hello-world.cc"],
        # 要链接到二进制目标的其他库的列表
        deps = [
            ":hello-greet",  # 同一包下可省略包路径和//
            "//lib:hello-time",  # 不同包下必须严格按照 //包路径:目标名 的标签写法
        ],
    )
    

    目标间的依赖关系如下:

    “hello-world”的依赖关系图显示了文件修改后依赖关系的变化。

  3. 执行构建:在 stage3 目录下,执行:

    bazel build //main:hello-world
    

    Bazel 会生成如下内容:

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

    现在已经构建完成了,继续执行 bazel-bin/main/hello-world 即可运行 hello-world 程序

Bazel 核心知识

工作区 (workspace)

  • 一个 Workspace 就可以认为就是一个独立的 C/C++ Project。譬如上面 cpp-tutorial 目录下分别由 stage1stage2stage3 三个项目,每个项目的根目录下有一个 WORKSPACE 文件(空的就行)。
  • Bazel 会将包含一个 WORKSPACEWORKSPACE.bazel 文件的目录识别为一个项目,每个项目之间互不干扰是完全独立的。
    • 可以同时包含 WORKSPACEWORKSPACE.bazel,此时 .bazel 那个优先级更高。
  • 一个 Workspace 里可以包含多个 Packages (包),每个 Package 中包含一组相关的源文件和一个 BUILD 文件。BUILD 文件指定可以从源代码构建哪些输出。例如,stage3 下就包含了两个 Package:mainlib
  • 工作区有时也叫代码库。

BUILD & 包

  • 软件包 (包) 指的是包含名为 BUILDBUILD.bazel 的 BUILD 文件的目录。

    • 可以同时包含 BUILDBUILD.bazel,此时 .bazel 那个优先级更高。
  • 软件包包含其目录中的所有文件,以及其下的所有子目录,但那些本身包含 BUILD 文件的子目录除外。根据此定义,任何文件或目录都不能包含在两个不同的软件包中。

    例如,以下目录树中有两个软件包:my/app 和子软件包 my/app/tests。请注意,my/app/data 不是软件包,而是属于软件包 my/app 的目录。

    src/
    └─ my
        └─ app
            ├─ BUILD
            ├─ app.cc
            ├─ data
            │   └─ input.txt
            └─ tests
                ├─ BUILD
                └─ test.cc
    
  • BUILD 文件采用 Starlark 语言对模块构建进行描述,语法类似于 Python

    • 每个 BUILD 文件都需要至少一条规则 (rule) 作为一组指令,告诉 Bazel 如何构建所需的输出,例如可执行文件或库。
    • BUILD 文件中定义的规则 (rule) 的实例都称为一个目标 (target),并指向一组特定的源文件和依赖项。 目标还可以指向其他目标。从逻辑上来说即每个 package 可以包含多个 targets,而具体的 target 则采用 Starlark 语法定义在一个 BUILD 文件中。

BUILD 文件核心语法

规则 (rule)
  • 规则用于在 BUILD 文件(例如 cc_library)中定义如何生成一个目标 (target)。从 BUILD 文件作者的角度来看,规则由一组属性和黑盒逻辑组成。

  • 在简单的 BUILD 文件中,规则声明可以随意重新排序,而不改变行为。

  • bazel 定义了很多原生规则,可以直接在 BUILD 文件中使用,而无需 load 语句引入

    • 可以在 .bzl 文件中自定义规则,并在 BUILD 中用 load 语句引入。

    • 原生规则可以在 .bzl 文件中需要使用 native 模块来引用(如 native.cc_binary),但在 BUILD 文件中原生规则可以直接使用。

    • 详细的各项原生规则及其API见文档:Bazel 构建函数百科全书 (google.cn)。

      在这里插入图片描述

    • 我们常用的原生规则包括 cc_binarycc_library 等,分别用来构建二进制可执行程序和库(静态库/动态库)。

      # 例子:在BUILD中使用bazel内置的原生规则: cc_binary
      cc_binary(
          name = "hello-world",
          srcs = ["hello-world.cc"],
          deps = [
              ":hello-greet",
              "//lib:hello-time",
          ],
      )
      
  • 大多数规则都具有类似的命名方案。例如,cc_binarycc_librarycc_test 分别是 C++ 二进制文件、库和测试的构建规则。其他语言会采用相同的命名方案,但采用不同的前缀,例如适用于 Java 的 java_*

    • *_binary 规则可用于构建给定语言的可执行程序。
    • *_test 规则是 *_binary 规则的专用规则,用于自动测试。测试只是在成功时返回零的程序。
    • *_library 规则以指定给定的编程语言指定单独编译的模块。库可以依赖于其他库,二进制文件和测试可以依赖于库,并且具有预期的单独编译行为。
  • 一个规则一般具有很多属性(见后面的小节)。

目标 (target)
  • 规则和目标是定义和实现的关系。也就是说,目标是规则的一个实例

    • 一个 Rule 由很多 attribute 构成,这点采用面向对象的概念来看,Rule 就好比是 class,而 attribute 就好比是 class 的 member。

    • 下面这段代码实际上就是定义了一个 target,每个实例必须要有一个名字在同一个 package 中和其他 target 实例进行区分。所以 name 这个 attribute 是必须有的,其他 attribute 是可选的,不写则按默认值定义。

      # 例子:定义一个name为"hello-world"的target,它是cc_binary规则的一个实例 
      cc_binary(
          name = "hello-world",
          srcs = ["hello-world.cc"],
          deps = [
              ":hello-greet",
              "//lib:hello-time",
          ],
      )
      
  • 可以使用标签来唯一标识一个目标(详下节)。

标签 (label)
  • 标签是目标的标识符。简单来说,标签就是唯一标识一个 target 的 ID

  • 大部分情况下,我们引用的都是同一个 workspace 中的 target,此时标签的语法如下

    //path/to/package:target-name
    
    • // 开始,接下来的 path/to/package 也就是这个 target 所在 package 在 workspace 中的相对路径。然后是一个 : 后面跟着一个 target-name 即上面说的一个 target 中的 name 那个属性的字符串值。

    • 如果要引用不同 workspace 中的 target,就必须使用标签的完整语法,见:标签 | Bazel (google.cn)

    • 如果是引用同一个包中的 target,那么标签语法可进一步简化,以下两种方式均可:

      //:target-name
      :target-name
      
  • 特别地,可以使用 //path/to/package:__pkg__ 来表示一个包下地所有 target。

属性 (attribute) 及依赖
  • 属性是规则的参数,用于表示每个目标的 build 信息。如果在 BUILD 中实例化规则时没有显式指定某个属性的值,则该属性会使用默认值。

  • 大多数规则常见的属性包括 names (必需)、srcsdepsdatavisibilityincludescopts等,它们分别声明目标的源文件、依赖项和自定义编译器选项。给定目标可用的特定属性取决于其规则类型。

  • 原生规则的具体属性需要参见文档:Bazel 构建函数百科全书 (google.cn)。以 cc_library 规则为例,说明它的一些常用属性:

    • names: 目标的唯一名称。
    • deps: 此库所依赖的其他库的列表(可以通过标签来引用)。
    • srcs: 为创建库目标而处理的 C 和 C++ 文件的列表,包括源文件和头文件
    • data: 此库在运行时所需的文件列表。
    • hdrs: 伴随此库发布的头文件,并且可以被其他依赖这个库的目标(如其他 cc_librarycc_binary)使用,Bazel 在构建过程中会确保这些头文件能够被正确找到和使用。cc_binary等规则是没有此属性的。
    • visibility: 指定此库在其他库中的可见性(可以通过标签来引用)。默认情况下,一个目标只对相同库中的其他目标显式可见。
    • includes: 要添加到编译行的 include 目录列表。
    • copts: 将这些选项添加到 C++ 编译命令中。比如这里可以写 -Imy_libpath 来将 my_libpath 加入编译时的头文件搜索路径,可以写 -pthread 来表明使用了多线程库。
      • includescopts 中设置 -I 都可以指定头文件位置。但是,前者会为该规则即依赖该规则的所有规则都设置头文件位置,而后者只会为该规则设置头文件位置(因为本身只是一次编译命令的选项)。

    注意到这里并没有指定生成 .a 静态库还是 .so 动态库,实际上静态库还是动态库由引用此库的 cc_binary 规则决定,具体来说,cc_binary 可以指定 linksharedlinkstatic 为 True 还是 False 来决定链接时使用动态库还是静态库。默认情况下,linkshared 是 False,linkstatic 是 True。

    此外需要说明的是,srcsdatahdrsincludes 等属性中设置的地址都是相对于当前包路径而言的,即相对于当前 BUILD 文件所处的目录

    以下给出一个示例:最终目标是构建可执行程序 foo,它依赖于源文件 foo.cc 和头文件 foo.h,同时还依赖于库 bar。库 bar 则依赖于源文件 bar.cc 和头文件 bar-impl.h,它同时还依赖于另一个库 baz,库 bar 中的接口由 bar.h 这个头文件所定义。另一个库 baz 依赖于源文件 baz.ccbaz-impl.h,库 baz 中的接口由 baz.h 这个头文件所定义。

    cc_binary(
        name = "foo",
        srcs = [
            "foo.cc",
            "foo.h",
        ],
        deps = [":bar"],
    )
    
    cc_library(
        name = "bar",
        srcs = [
            "bar.cc",
            "bar-impl.h",
        ],
        hdrs = ["bar.h"],
        deps = [":baz"],
    )
    
    cc_library(
        name = "baz",
        srcs = [
            "baz.cc",
            "baz-impl.h",
        ],
        hdrs = ["baz.h"],
    )
    
  • 在 srcs, deps 等依赖属性中,可以使用 bazel 提供的 glob 函数来查找与特定路径模式匹配的所有文件,详细语法见:glob

构建命令

详细的各项参数见:使用 Bazel 构建程序 (google.cn)

使用 bazel build 来完成对目标的构建:

# 以 // 开头的所有目标模式都是相对于当前工作区而言的。
bazel build //path/to/package:target-name

# 以 // 开头的目标模式会根据当前的工作目录进行解析。
bazel build path/to/package:target-name

案例:

# 构建workspace下的foo/bar包中的wiz目标
bazel build //foo/bar:wiz

# 构建workspace下的foo/bar包中的bar目标,等同于 //foo/bar:bar
bazel build //foo/bar

# 构建workspace下的foo/bar包中的全部目标
bazel build //foo/bar:all

# 构建当前目录下定义的foo目标
bazel build :foo

# 构建当前目录下的bar子目录下定义的foo目标
bazel build bar:wiz

参考文献

  • Google Bazel 官方教程
  • bazel工程介绍和demo构建_bazel构建方式图-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024怎么选蓝牙耳机性价比品牌?四款小白必看优选王炸机型推荐!

如今国人对蓝牙耳机的选择日益重视,蓝牙耳机作为智能的手机备件,普及率也是一升再升,但市面上很多品牌虽然宣传自己音质好和佩戴感舒适,其实性能不佳,那么2024怎么选蓝牙耳机性价比品牌?身为资深的蓝牙耳机…

复变函数在大模型中的应用

1. 导入 说来惭愧,我研究生时的研究方向是复分析,但毕业近十年来几乎没用到它。 我还记得实习时做自我介绍时,我说我的研究方向是复分析。面试官不太了解,我便解释说,这是关于对 -1 开平方得到的虚数 i 的研究。 在…

信号的捕捉

1.信号的产生 信号递达:实际执行信号的处理动作称为信号的递达 信号未决:信号从产生到递达之间的状态 进程可以阻塞某个信号 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作 注意,忽略和阻塞是不同的,只要信号阻塞就不会被递达,而忽略是递达…

【Apache Doris】数据均衡问题排查指南

原文阅读:【巨人肩膀社区博客分享】【Apache Doris】数据均衡问题排查指南 一、前提概要 当集群处于以下几种情况时,可参考本文进行问题排查。 •BE节点之间的数据不均 •单个BE节点上的多个磁盘之间的数据不均 •BE节点的上线和下线进度卡死&#…

《高等代数》两条线行列式

说明:此文章用于本人复习巩固,如果也能帮助到大家那就更加有意义了。 注:两条线行列式的固定做法为按照第一列展开。

Kafka分布式集群部署实战:跨越理论,直击生产环境部署难题与解决方案,性能调优、监控与管理策略大揭秘,轻松上手分布式消息中间件

本文介绍kafka的集群如何部署和安装,1-4章理论知识,第5章详解集群的部署,部署Kafka之前需要先部署好分布式的Zookeeper,不喜欢理论的可以直接看第5章,欢迎大家一起探讨技术! Zookeeper集群部署参考文章&…

VUE-组件间通信(三)全局事件总线

一、作用&#xff1a;任意组件间通信 二、实现 1、创建全局事件总线 new Vue({render: h > h(App),beforeCreate(){//创建全局事件总线Vue.prototype.$busthis} }).$mount(#app) 2、学生组件 触发事件 <template><div class"studentInfo"><h…

(备份)常用ASCII 8*8 点阵 以及查询显示字符的点阵

图片 #include "driver/spi_master.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "string.h" #include "driver/i2c.h" #include "esp_rom_sys.h"…

【postman如何生成python代码】

postman如何生成python代码 https://jingyan.baidu.com/article/86fae3461577c27d48121ad1.html

【大模型系列篇】词向量 - 从Word2Vec到ELMo

&#x1f525;&#x1f525;&#x1f525;首先安利一个比较不错的忍不住就想一键三连的大模型科普: 大模型科普专栏 - AI老兵文哲&#xff08;哔哩哔哩&#xff09; 词向量&#xff08;又叫词嵌入&#xff09;已经成为NLP领域各种任务的必备一步&#xff0c;而且随着BERT、GPT等…

JS打造一款你自己的专用字体:使用p5.js与JavaScript实现

前言 在最近的生成艺术项目中遇到一个小问题&#xff1a;如何在作品中优雅地添加文本元素&#xff0c;同时避免使用网络字体&#xff0c;要么侵权要么花钱~~给项目增加不必要的负担&#xff0c;我决定不走寻常路&#xff0c;自己动手&#xff0c;丰衣足食&#xff0c;用JS打造…

C++11 新特性基础

前言 C11是继&#xff0c;C98/03之后的最大的一次更新&#xff0c;这次更新虽然花了很长的时间&#xff0c;但是真的推出了很多的干货&#xff01;本期内容开始我们将介绍C11常用的操作&#xff01; 目录 前言 一、C11介绍 二、统一的列表初始化 1、{}初始化 2、std::in…

【机器学习】循环神经网络(RNN)介绍

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 循环神经网络(RNN)介绍什么是RNN?RNN的基本原理递归神经网络单元前向传播反向传…

Ubuntu下安装和配置MQTT服务器Mosquitto

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的通信协议&#xff0c;设计用于物联网设备之间的低带宽、不稳定网络环境下的高效通信。MQTT允许设备通过发布&#xff08;publish&#xff09;和订阅&#xff08;subscribe&#xff09;模式进行消…

清华2024内地录取3500人,其中900多人是走这个政策进来的... ...

2024年&#xff0c; 清华大学共录取本科新生3800余人&#xff0c;其中内地学生3500余人&#xff0c;覆盖全国31个省份1000 多所生源中学;港澳台学生60余人&#xff0c;国际学生约230人。 大李露个脸 清华大学2024年新生数据 普通批提前批共录取1549人&#xff0c;占比44% 强基计…

火龙果检测-目标检测数据集(包括VOC格式、YOLO格式)

火龙果检测-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1NdRBsHnYCK9xZd7bzQoN5w?pwd779l 提取码&#xff1a;779l 数据集信息介绍&#xff1a; 共有 1106 张图像和一一对应的标注文件 标注…

macos OneNote 2016 for Mac 官方pkg下载地址 - macos 10.15 Catalion 可用Onenote版本官方下载地址

macos 10.15 Catalion 版本的系统已经无法正常从应用商店下载到可用的Onenote 应用,原因是版本不受支持, 而且onenote官方链接的应用商店地址https://apps.apple.com/us/app/microsoft-onenote/id784801555?mt12在中国地区也无法访问, 所以中国地区用户如果想使用onenote应用…

【云原生系列之SkyWalking的部署】

1、分布式链路追踪 1.1概念 在较大的web集群和微服务环境中&#xff0c;客户端的一次请求需要经过不同的模块&#xff0c;多个不同中间件&#xff0c;多个不同机器一起相互协作才能处理完成客户端的请求&#xff0c;而在这一系列的请求过程之中,处理流程可能是串行执行,也可能…

华为云征文|基于Flexus云服务器X实例之安装长亭雷池waf教程

&#x1f534;大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 先看这里 写在前面何为长亭雷池waf安装社区版雷池雷池环境要求查看华为云Flexus云服务器X实例的配置一条命令安装雷池waf检查查看是否安装成功 雷池使用登录雷池配置站点 写在…

51单片机.之ADC数字模拟转换

1、数字转模拟电路&#xff0c;输出波形&#xff0c;示波器采集来显示波形 单片机通过i2c给&#xff0c;模数转换器&#xff0c;写入数字信号&#xff0c;定时器1s扫描按键的切换 1、key.c 切换波形 #include <reg52.h>sbit KEY_IN_1 P2^4; sbit KEY_IN_2 P2^5; …