muduo 源码中关于工程创建编译cmake 到Bzael 创建工程
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++ 目标可以依赖此规则。