blj28

导航

muduo 源码中关于工程创建编译cmake 到Bzael 创建工程

Bazel官方教程 -- 构建C++工程基础知识

Bazel官方教程 – 构建C++工程基础知识

需求:因项目需要,将Cmake编译方式改为Bazel。

官方参考:https://docs.bazel.build/versions/main/tutorial/cpp.html
Bazel教程:构建C++工程

本教程涵盖了使用 Bazel 构建 C++ 应用程序的基础知识。 您将设置您的工作区并构建一个简单的 C++ 项目,该项目说明了关键的 Bazel 概念,例如 目标 和 BUILD 文件。 完成本教程后,请查看 Common C++ Build Use Cases 以获取有关更高级内容的信息,例如编写和运行 C++ 测试。
预计完成时间:30 分钟。
0. 你将学习到的内容

在本教程中,你将学会怎样:

    构建一个目标
    可视化工程依赖
    将项目分成多个目标和包
    通过包控制目标可视化
    通过标签引用目标

1. 开始之前

为了教程做准备,如果您还没有安装Bazel,请先安装它。 然后,从 Bazel 的 GitHub 存储库中检索示例项目:git clone 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

   

如您所见,共有三组文件,每组代表本教程中的一个阶段。 在第一阶段,您将构建单个包中的单个目标。 在第二阶段,您将项目拆分为多个目标,但将其保存在一个包中。 在第三个也是最后一个阶段,您将项目拆分为多个包并使用多个目标构建它。
2. 使用Bazel进行构建
2.1 建立工作空间

在构建项目之前,您需要设置其工作空间。 工作空间是一个包含项目源文件和 Bazel 构建后文件输出的目录。 它还包含 Bazel 认为特殊的文件:

    WORKSPACE 文件,它将目录及其中的内容标记为 Bazel 工作区,并位于项目目录结构的根目录中,
    可以使用一个或多个 BUILD 文件,它们告诉 Bazel 如何构建项目的不同部分。 (工作空间中包含 BUILD 文件的目录称为一个包(package)。您将在本教程的后面部分了解包相关知识)

2.2 理解Build文件

一个 BUILD 文件包含几种不同类型的 Bazel 指令。 最重要的类型是构建规则,它告诉 Bazel 如何构建所需的输出,例如可执行的二进制文件或库。 BUILD 文件中构建规则的每个实例称为目标。一个目标可以指向一组特定的源文件和依赖项, 也可以指向其他目标。

看一下cpp-tutorial/stage1/main目录下的BUILD文件:

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



在我们的示例中,hello-world 目标实例化了 Bazel 的内置 cc_binary 规则。 该规则告诉 Bazel 从 hello-world.cc 源文件构建一个独立的,没有依赖关系的可执行二进制文件。

目标中的属性明确声明其依赖项和选项。 虽然 name 属性是强制性的,但许多是可选的。 例如,在 hello-world 目标中,name 是必需的且不言自明,而 srcs 是可选的,意味着指定 Bazel 从中构建目标的源文件。
2.3 构建工程

为了构建您的示例项目,请跳转到 cpp-tutorial/stage1 目录并运行:

cd examples/cpp-tutorial/stage1/
bazel build //main:hello-world

 

在目标标签中,//main: 是 BUILD 文件在工作空间根目录的位置,hello-world 是BUILD 文件中的目标名称(name)。 (您将在本教程末尾更详细地了解目标标签。)

Bazel 产生类似于以下内容的输出:

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

    1
    2
    3
    4
    5
    6
    7
    8

恭喜,您刚刚构建了您的第一个 Bazel 目标! Bazel 将构建的输出文件放在工作空间根目录的 bazel-bin 目录中。 浏览其内容以了解 Bazel 的输出结构。

现在测试您新构建的二进制文件:

bazel-bin/main/hello-world

    1

2.4 查看依赖关系图

一次成功的构建所包含的依赖项都会在 BUILD 文件中明确说明。 Bazel 使用这些语句来创建项目的依赖关系图,从而实现准确的增量构建。

要可视化示例项目的依赖关系,您可以通过在工作区根目录运行如下命令,来生成依赖关系图的文本表示:

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

    1

结果如下:

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

 

上面的命令告诉 Bazel 查找目标 //main:hello-world 的所有依赖项(不包括主机和隐式依赖项)并将输出格式化为图形。

然后,将文本粘贴到 GraphViz 中。

在 Ubuntu 上,您可以通过安装 GraphViz 和 xdot Dot Viewer 在本地查看图形:

sudo apt update && sudo apt install graphviz xdot

    1

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

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

    1

如您所见,示例项目的第一阶段,只有一个单一的目标,它构建一个没有额外依赖项的单一源文件:
图片
在设置好工作区、构建项目并检查其依赖项之后,您可以添加一些复杂的东西。
3. 完善你的Bazel构建项目

虽然单个目标对于小型项目就足够了,但您可能希望将较大的项目拆分为多个目标和包,以允许快速增量构建(即仅重建已更改的内容)并通过构建项目的多个部分来加速构建。
3.1 指定多个构建目标

您可以将示例项目构建拆分为两个目标,看一下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 目标中的 deps 属性告诉 Bazel,构建 hello-world 二进制文件需要 hello-greet 库。

接下来,构建这个新版本的项目。 切换到 cpp-tutorial/stage2 目录并运行以下命令:

cd ../stage2
bazel build //main:hello-world


Bazel 产生类似于以下内容的输出:

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


现在你可以再次测试你构建的二进制文件:

bazel-bin/main/hello-world


如果您现在修改 hello-greet.cc 并重新构建项目,Bazel 只会重新编译该文件。

查看依赖图,您可以看到 hello-world 依赖于与之前相同的输入,但构建的结构不同:
图片
您现在已经使用两个目标构建了项目。 hello-world 目标构建一个源文件并依赖于另一个目标 (//main:hello-greet),该库是由两个额外的源文件构建的。
4. 使用多个包

您可以将项目拆分为多个包,看一下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,工作区现在包含两个包: lib 和 main。

看一下 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",
    ],
)


如您所见,主包中的 hello-world 目标依赖于 lib 包中的 hello-time 目标(因此目标标签为 //lib:hello-time) - Bazel 通过 deps 属性知道这一点。 看一下依赖图:
图片
请注意,为使构建成功,您使用 visibility 属性使 lib/BUILD 中的 //lib:hello-time 目标对 main/BUILD 中的目标显式可见。 这是因为默认情况下,目标仅对同一 BUILD 文件中的其他目标可见。 (Bazel 使用目标可见性来防止诸如包含实现细节的库泄漏到公共 API 中的问题。)

接下来,您可以构建该项目的最终版本。 切换到 cpp-tutorial/stage3 目录并运行以下命令:

cd ../stage3/
bazel build //main:hello-world


Bazel 产生类似于以下内容的输出:

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

    1
    2
    3
    4
    5
    6
    7
    8

现在你可以再次测试你构建的二进制文件:

bazel-bin/main/hello-world


您现在已经将项目构建为具有三个目标的两个包,并了解它们之间的依赖关系。
5. 使用标签引用目标

在 BUILD 文件和命令行中,Bazel 使用标签来引用目标 - 例如,//main:hello-world 或 //lib:hello-time。 它们的语法是:

//path/to/package:target-name

    1

如果目标是规则目标,那么 path/to/package 是++从工作空间根目录(包含 WORKSPACE 文件的目录)到包含 BUILD 文件的目录的路径++,target-name 是您在 BUILD 中命名的目标文件(++名称属性++)。 如果目标是文件目标,那么 path/to/package 是包所在++根目录的路径++,target-name 是目标文件的名称,需要包括它相对于根目录的完整路径( BUILD 文件所在目录)。

在存储库根目录引用目标时,包路径为空,只需使用 //:target-name。 在同一个 BUILD 文件中引用目标时,您甚至可以跳过 // ++工作区根标识符++,而只需使用 :target-name。
6. 深入阅读

恭喜! 您现在了解了使用 Bazel 构建 C++ 项目的基础知识。 接下来,阅读最常见的 C++ 构建用例。

有关更多详细信息,请参阅:

    外部依赖项以了解有关使用本地和远程存储库的更多信息。
    了解更多关于 Bazel 的其他规则。
    Java 构建教程开始使用 Bazel 构建 Java 应用程序。
    开始使用 Bazel 为 Android 构建移动应用程序的 Android 应用程序教程。
    iOS 应用程序教程开始使用 Bazel 为 iOS 构建移动应用程序。

构建愉快!
————————————————
版权声明:本文为CSDN博主「Darchan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36354875/article/details/124822985

Bazel官方教程 – 通用C++使用案例

官方参考:https://docs.bazel.build/versions/main/cpp-use-cases.html

在这里,您将找到一些使用 Bazel 构建 C++ 项目的最常见用例。 如果您还没有学习构建 C++ 项目,请先完成教程 Bazel官方教程 – 构建C++工程基础知识,然后再来循序渐进学习,下文标黄色的重点是与官方提供代码有出入的细节。

有关 cc_library 和 hdrs 头文件的信息,请参阅 cc_library

1. 在一个目标中包含多个文件

您可以使用 glob 在单个目标中包含多个文件。 例如:

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

使用此目标,Bazel 将找寻 BUILD 文件相同的目录下所有 .cc 和 .h 文件(不包括子目录),来构建项目。

2. 使用传递包含

如果文件包含头文件,则文件的规则应取决于该头文件的库。 相反,仅需要将直接依赖项指定为依赖项。 例如,假设sandwich.h 包含bread.h,而bread.h 包含flour.h。sandwich.h 不包括floor.h(谁想要 floor.h 在sandwich.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"],
)
 

这里sandwich库依赖于bread库,bread库又依赖于flour库。

3. 添加包含目录

有时您不能(或不想)在工作空间根目录下,具有包含目录。 可能存在一个有包含目录的库,该目录与其在您的工作区的路径不一致。 例如,假设您具有以下目录结构:

└── my-project  ## 工作区所在目录
    ├── legacy    
    │   └── some_lib  ## 库所在目录
    │       ├── BUILD
    │       ├── include
    │       │   └── some_lib.h
    │       └── some_lib.cc
    └── WORKSPACE

Bazel 期望 some_lib.h 被包含,正常使用的话,需要写作如下形式: legacy/some_lib/include/some_lib.h,但假设 some_lib.cc 直接使用 #include “some_lib.h”, 为了使包含路径有效,legacy/some_lib/BUILD 需要指定 some_lib/include 目录是一个包含目录

cc_library(
    name = "some_lib",
    srcs = ["some_lib.cc"],
    hdrs = ["include/some_lib.h"],
    copts = ["-Ilegacy/some_lib/include"],
)

 

这对于外部依赖项特别有用,因为它们的头文件必须包含在 / 前缀中。

4. 包含外部库

假设您正在使用 Google Test。 您可以在 WORKSPACE 文件下使用仓库功能中的一个函数,去下载 Google Test 并
使其在您的仓库中可用:

// 添加在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.10.0.zip",
    sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91",
    build_file = "@//:gtest.BUILD",
)

注意:如果目标已包含 BUILD 文件,则可以省略 build_file 属性。

然后创建 gtest.BUILD文件,一个用于编译 Google Test 的 BUILD 文件。 Google Test 有几个“特殊”要求,使其 cc_library 规则更加复杂:

  • 关于googletest-release-1.10.0/src/*.cc 所有文件,需要在编译时,排除googletest-release-1.10.0/src/gtest-all.cc 以防止重复符号的链接错误。
  • 它使用的头文件(“gtest/gtest.h”),是相对于 googletest-release-1.10.0/include/ 目录的,因此您必须将该目录添加到包含路径中。
  • 它需要在 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"],
)
 

这有点混乱:所有内容都以 googletest-release-1.10.0 为前缀,作为archive结构的副产品。 您可以通过添加 strip_prefix 属性使 http_archive 去除此前缀:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# 注意此处官方版本为1.10.0,推荐使用下面的1.7.0
http_archive(
    name = "gtest",
    build_file = "@//:gtest.BUILD",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    strip_prefix = "googletest-release-1.7.0",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
)

注意:使用官网的1.10.0,会报类似gcc failed: error executing command /usr/bin/gcc @bazel-out/k8-fastbuild/bin/test-2.params错误。

然后 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"],
)

现在 cc_ 的相关规则 可以依赖 @gtest//:main

5. 编写和运行 C++ 测试

例如,您可以创建一个测试 ./test/hello-test.cc,例如:

#include "gtest/gtest.h"
#include "main/hello-greet.h"

TEST(HelloTest, GetGreet) {
  EXPECT_EQ(get_greet("Bazel"), "Hello Bazel");
}

然后为您的测试,创建 ./test/BUILD 文件:

cc_test(
    name = "hello-test",
    srcs = ["hello-test.cc"],
    copts = ["-Iexternal/gtest/include"],
    deps = [
        "@gtest//:main",
        "//main:hello-greet",
    ],
)

要使 hello-greet 对 hello-test 可见,您必须将“//test:pkg”添加到 ./main/BUILD 中的可见属性。

当前的目录结构为:

├── BUILD(空文件夹,**必须**,否则会提示找不到gtest.BUILD同层目录下的BUILD文件)
├── gtest.BUILD
├── main
│   ├── BUILD
│   ├── hello-greet.cc
│   ├── hello-greet.h
│   └── hello-world.cc
├── test
│   ├── BUILD
│   └── test.cc
└── WORKSPACE

现在您可以使用 bazel test 来运行测试。

bazel test test:test
bazel test --test_output all //test:test    # 查看测试输出结果

这会产生以下输出:

INFO: Analyzed target //test:test (0 packages loaded, 0 targets configured).
INFO: Found 1 test target...
Target //test:test up-to-date:
  bazel-bin/test/test
INFO: Elapsed time: 0.104s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
//test:test                                                     (cached) PASSED in 0.0s

Executed 0 out of 1 test: 1 test passes.
There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings commINFO: Build completed successfully, 1 total action

注意:

6. 对预编译库添加依赖

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

cc_library(
    name = "mylib",
    srcs = ["mylib.so"], 
    hdrs = ["mylib.h"],
)

这样,您工作区中的其他 C++ 目标可以依赖此规则。

 

 

posted on 2023-02-13 21:14  bailinjun  阅读(245)  评论(0编辑  收藏  举报