本教程介绍了使用
Bazel 构建 Java 应用的基础知识。您将设置工作区并构建一个简单的 Java 项目,该项目
说明了关键 Bazel 概念,例如目标和 BUILD 文件。
预计完成时间:30 分钟。
学习内容
在本教程中,您将学习如何:
- 构建目标
- 直观呈现项目的依赖项
- 将项目拆分为多个目标和软件包
- 控制软件包中的目标可见性
- 通过标签引用目标
- 部署目标
准备工作
安装 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 文件
A BUILD 文件包含多种不同类型的 Bazel 指令。
最重要的类型是 构建规则,用于告知 Bazel 如何构建
所需的输出,例如可执行二进制文件或库。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 文件中明确声明构建依赖项。 Bazel 使用这些语句创建项目的依赖关系图,从而 实现准确的增量构建。
如需直观呈现示例项目的依赖项,您可以在工作区根目录下运行以下命令,以生成依赖关系图的文本 表示形式:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph上述命令告知 Bazel 查找目标
//:ProjectRunner的所有依赖项(不包括主机和隐式依赖项),并将
输出格式设置为图。
然后,将文本粘贴到 GraphViz 中。
如您所见,该项目有一个目标,该目标构建两个源文件,没有其他依赖项:
设置工作区、构建项目并检查其 依赖项后,您可以添加一些复杂性。
优化 Bazel 构建
虽然单个目标对于小型项目来说就足够了,但您可能需要将 大型项目拆分为多个目标和软件包,以便进行快速增量 构建(即仅重新构建更改的内容),并通过一次构建项目的多个部分来加快构建速度。
指定多个构建目标
您可以将示例项目构建拆分为两个目标。将
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,
构建 ProjectRunner 二进制文件需要 greeter 库。
如需构建此新版本的项目,请运行以下命令:
bazel build //:ProjectRunnerBazel 生成的输出类似于以下内容:
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 依赖于与之前相同的输入,但构建的结构不同:
您现在已使用两个目标构建了项目。ProjectRunner 目标构建
两个源文件,并依赖于另一个目标 (: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 属性了解这一点。
请查看依赖关系图:
不过,如需成功构建,您必须使用 visibility 属性明确授予 //src/main/java/com/example/cmdline/BUILD 中的 runner 目标
对
//BUILD 中的目标的可见性。这是因为,默认情况下,目标
仅对同一 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:runnerBazel 生成的输出类似于以下内容:
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 目标以进行部署。这样,您就可以在开发环境之外运行二进制文件。
您可能还记得,java_binary 构建规则
会生成一个 .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.jarBazel 生成的输出类似于以下内容:
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 的内容:
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 依赖项的 规则。
外部依赖项,详细了解如何使用 本地和远程代码库。
C++ 构建教程,开始使用 Bazel 构建 C++ 项目。
Android 应用教程和 iOS 应用教程,开始使用 Bazel 构建 Android 和 iOS 移动应用。
祝您构建愉快!