本教程涵盖了使用
Bazel。您将设置自己的工作区并构建一个简单的 Java 项目,
说明了关键的 Bazel 概念,例如目标和 BUILD
文件。
预计完成时间:30 分钟。
学习内容
在本教程中,您将学习如何:
- 构建目标
- 直观呈现项目的依赖项
- 将项目拆分为多个目标和软件包
- 控制各个软件包中的目标可见性
- 通过标签引用目标
- 部署目标
准备工作
安装 Bazel
为学习本教程做好准备,如果您尚未安装 Bazel,请先安装 Bazel。
安装 JDK
安装 Java JDK(首选版本为 11,但支持版本 8 到 15)。
将 JAVA_HOME 环境变量设置为指向 JDK。
在 Linux/macOS 上:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
在 Windows 上:
- 打开“控制面板”。
- 依次前往“系统和安全”>“系统”>“高级系统设置”>“高级”标签页 >“环境变量...”.
- 在“用户变量”列表(顶部的列表)下,点击“新建...”
- 在“变量名称”字段中,输入
JAVA_HOME
。 - 点击“浏览目录...”
- 前往 JDK 目录(例如
C:\Program Files\Java\jdk1.8.0_152
)。 - 点击“确定”。
获取示例项目
从 Bazel 的 GitHub 代码库中检索示例项目:
git clone https://github.com/bazelbuild/examples
本教程的示例项目位于 examples/java-tutorial
目录,其结构如下:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
使用 Bazel 构建
设置工作区
您需要先设置项目的工作区,然后才能构建项目。工作区是一个目录,用于存放项目的源文件和 Bazel 的构建输出。它 还包含 Bazel 识别为特殊的文件:
WORKSPACE
文件,用于将目录及其内容标识为 Bazel 工作区位于项目目录结构的根目录下,一个或多个
BUILD
文件,告知 Bazel 如何构建 项目。(工作区中包含BUILD
文件的目录就是一个软件包。您将在本教程稍后的部分了解软件包。)
如需将某个目录指定为 Bazel 工作区,请在该目录中创建一个名为 WORKSPACE
的空文件。
当 Bazel 构建项目时,所有输入和依赖项都必须位于同一工作区中。位于不同工作区的文件独立于一个工作区 否则就会出现这种错误,这超出了本教程的讨论范围。
了解 BUILD 文件
BUILD
文件包含多种类型的 Bazel 指令。最重要的类型是“构建规则”,它告诉 Bazel 如何构建
所需的输出,例如可执行二进制文件或库。BUILD
文件中 build 规则的每个实例都称为目标,并指向一组特定的源文件和依赖项。目标还可以指向
目标。
查看 java-tutorial/BUILD
文件:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
在我们的示例中,ProjectRunner
目标会实例化 Bazel 的内置 java_binary
规则。该规则会告知 Bazel 构建 .jar
文件和封装容器 shell 脚本(二者均以目标命名)。
目标中的属性会明确说明其依赖项和选项。虽然 name
属性是必需属性,但许多属性都是可选属性。例如,在 ProjectRunner
规则目标中,name
是目标的名称,srcs
指定 Bazel 用于构建目标的源文件,main_class
指定包含 main 方法的类。(您可能已经注意到,我们的示例使用 glob 将一组源文件传递给 Bazel,而不是逐个列出这些文件。)
构建项目
如需构建示例项目,请前往 java-tutorial
目录
并运行:
bazel build //:ProjectRunner
在目标标签中,//
部分是 BUILD
文件相对于工作区根目录(在本例中为根目录本身)的位置,ProjectRunner
是 BUILD
文件中的目标名称。(您将在本教程的最后部分详细了解目标标签。)
Bazel 会生成类似于以下内容的输出:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
恭喜,您刚刚构建了您的第一个 Bazel 目标!Bazel 地点构建
输出位于工作区根目录下的 bazel-bin
目录中。浏览
以了解 Bazel 的输出结构。
现在,测试新构建的二进制文件:
bazel-bin/ProjectRunner
查看依赖关系图
Bazel 要求在 BUILD 文件中明确声明 build 依赖项。 Bazel 使用这些语句创建项目的依赖关系图,从而实现准确的增量构建。
如需直观呈现示例项目的依赖项,您可以在工作区根目录中运行以下命令,生成依赖项图的文字表示形式:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
上述命令会指示 Bazel 查找目标 //:ProjectRunner
的所有依赖项(不包括主机和隐式依赖项),并将输出格式设置为图表。
然后,将文本粘贴到 GraphViz 中。
如您所见,项目只有一个目标,使用 没有其他依赖项:
设置工作区、构建项目并检查其依赖项后,您可以增加一些复杂性。
优化 Bazel 构建
虽然单个目标对小型项目来说已经足够,但您可能希望进行拆分 大型项目拆分为多个目标和软件包,以实现快速增量 (即仅重新构建更改的内容),并通过以下方式加快构建速度: 一次性构建项目的多个部分。
指定多个构建目标
您可以将示例项目 build 拆分为两个目标。将
将 java-tutorial/BUILD
文件替换为以下代码:
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
采用此配置时,Bazel 会先构建 greeter
库,然后再构建 ProjectRunner
二进制文件。java_binary
中的 deps
属性会告知 Bazel 需要 greeter
库才能构建 ProjectRunner
二进制文件。
如需构建项目的这个新版本,请运行以下命令:
bazel build //:ProjectRunner
Bazel 会生成类似于以下内容的输出:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
现在,测试您新构建的二进制文件:
bazel-bin/ProjectRunner
如果您现在修改 ProjectRunner.java
并重新构建项目,Bazel 只会重新编译该文件。
查看依赖项图,您会发现 ProjectRunner
依赖于与之前相同的输入,但 build 的结构有所不同:
现在,您已构建了具有两个目标的项目。ProjectRunner
目标 build
两个源文件,并且依赖于另一个目标 (:greeter
),
一个额外的源文件
使用多个软件包
现在,我们将该项目拆分为多个软件包。如果您查看 src/main/java/com/example/cmdline
目录,会发现其中还包含一个 BUILD
文件以及一些源文件。因此,对于 Bazel
包含两个软件包://src/main/java/com/example/cmdline
和 //
(由于
工作区的根目录下有一个 BUILD
文件)。
查看 src/main/java/com/example/cmdline/BUILD
文件:
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
runner
目标依赖于 //
软件包中的 greeter
目标(因此
目标标签 //:greeter
)- Bazel 通过 deps
属性知道这一点。
查看依赖关系图:
不过,如需构建成功,您必须明确指定 runner
目标
//src/main/java/com/example/cmdline/BUILD
中的目标
//BUILD
(使用 visibility
属性)。这是因为
仅对同一 BUILD
文件中的其他目标可见。(Bazel 使用目标可见性来防止包含实现细节的库泄露到公共 API 等问题。)
为此,请将 visibility
属性添加到 greeter
目标中,
java-tutorial/BUILD
,如下所示:
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
现在,您可以在工作区的根目录中运行以下命令,构建新软件包:
bazel build //src/main/java/com/example/cmdline:runner
Bazel 会生成类似于以下内容的输出:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
现在,测试新构建的二进制文件:
./bazel-bin/src/main/java/com/example/cmdline/runner
现在,您已修改了项目,将其构建为两个软件包,每个软件包包含一个 并了解它们之间的依赖关系。
使用标签引用目标
在 BUILD
文件和命令行中,Bazel 使用目标标签来引用目标,例如 //:ProjectRunner
或 //src/main/java/com/example/cmdline:runner
。其语法如下所示:
//path/to/package:target-name
如果目标是规则目标,则 path/to/package
是包含 BUILD
文件的目录的路径,target-name
是您在 BUILD
文件中为目标指定的名称(name
属性)。如果目标是文件目标,则 path/to/package
是软件包根目录的路径,target-name
是目标文件的名称(包括其完整路径)。
引用代码库根目录中的目标时,软件包路径为空,只需使用 //:target-name
即可。引用同一 BUILD
中的目标时
您甚至可以跳过 //
工作区根标识符,直接使用
:target-name
。
例如,对于 java-tutorial/BUILD
文件中的目标,您无需执行以下操作:
请指定软件包路径,因为工作区根目录本身就是一个软件包 (//
);
您的两个目标标签就分别是 //:ProjectRunner
和 //:greeter
。
不过,对于 //src/main/java/com/example/cmdline/BUILD
文件中的目标,您必须指定 //src/main/java/com/example/cmdline
的完整软件包路径,并且目标标签为 //src/main/java/com/example/cmdline:runner
。
打包 Java 目标以进行部署
现在,我们将使用 运行时依赖项这样,您就可以在外部 开发环境。
如您所知,java_binary build 规则会生成 .jar
和封装容器 shell 脚本。查看
runner.jar
:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
内容如下:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
如您所见,runner.jar
包含 Runner.class
,但不包含其依赖项 Greeting.class
。Bazel 生成的 runner
脚本会添加 greeter.jar
添加到类路径,因此如果您按这样的方式处理,它会在本地运行,
无法在另一台机器上独立运行幸运的是,java_binary
规则
可让您构建一个可部署的独立二进制文件。要构建它,请将
_deploy.jar
附加到目标名称:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel 会生成类似于以下内容的输出:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
您刚刚构建了 runner_deploy.jar
,它可以单独运行,
因为其中包含所需的运行时
依赖项使用
与之前相同的命令:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
内容包括运行所需的所有类:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
深入阅读
如需了解详情,请参阅以下文档:
rules_jvm_external 用于 规则来管理传递 Maven 依赖项。
外部依赖项,详细了解如何使用本地和远程代码库。
其他规则,以详细了解 Bazel。
C++ 构建教程:开始进行构建 使用 Bazel 的 C++ 项目。
Android 应用教程和 iOS 应用教程,可帮助您开始使用 Bazel 构建 Android 和 iOS 移动应用。
祝大家尽情享受构建的乐趣!