Java 和 Bazel

本页面包含可帮助您将 Bazel 与 Java 项目搭配使用的资源。它链接到了一个教程、构建规则以及其他与使用 Bazel 构建 Java 项目相关的信息。

使用 Bazel

以下资源可帮助您在 Java 项目中使用 Bazel:

迁移到 Bazel

如果您目前使用 Maven 构建 Java 项目,请按照迁移指南中的步骤开始使用 Bazel 构建 Maven 项目:

Java 版本

有两个相关的 Java 版本,它们通过配置标志进行设置:

  • 代码库中源文件的版本
  • 用于执行代码和测试代码的 Java 运行时版本

配置代码库中源代码的版本

如果没有其他配置,Bazel 会假定代码库中的所有 Java 源文件都是使用单个 Java 版本编写的。如需指定代码库中源代码的版本,请将 build --java_language_version={ver} 添加到 .bazelrc 文件,其中 {ver} 例如为 11。Bazel 代码库所有者应设置此标志,以便 Bazel 及其用户可以引用源代码的 Java 版本号。如需了解详情,请参阅 Java 语言版本标志

配置用于执行和测试代码的 JVM

Bazel 使用一个 JDK 进行编译,并使用另一个 JVM 来执行和测试代码。

默认情况下,Bazel 使用其下载的 JDK 编译代码,并使用本地计算机上安装的 JVM 执行和测试代码。Bazel 使用 JAVA_HOME 或路径搜索 JVM。

生成的二进制文件与系统库中本地安装的 JVM 兼容,这意味着生成的二进制文件取决于计算机上安装的内容。

如需配置用于执行和测试的 JVM,请使用 --java_runtime_version 标志。默认值为 local_jdk

封闭测试和编译

如需创建封闭编译,您可以使用命令行标志 --java_runtime_version=remotejdk_11。代码将在从远程代码库下载的 JVM 上进行编译、执行和测试。如需了解详情,请参阅 Java 运行时版本标志

配置 Java 中构建工具的编译和执行

还有另一对 JDK 和 JVM 用于构建和执行工具,这些工具在构建流程中使用,但不在构建结果中。该 JDK 和 JVM 使用 --tool_java_language_version--tool_java_runtime_version 进行控制。默认值分别为 11remotejdk_11

使用本地安装的 JDK 进行编译

Bazel 默认使用远程 JDK 进行编译,因为它会替换 JDK 的内部组件。使用本地安装的 JDK 的编译工具链已配置,但未使用。

如需使用本地安装的 JDK 进行编译,即使用本地 JDK 的编译工具链,请使用其他标志 --extra_toolchains=@local_jdk//:all,但请注意,这可能不适用于任意供应商的 JDK。

如需了解详情,请参阅 配置 Java 工具链

最佳实践

除了 Bazel 的一般最佳实践 之外,以下是 Java 项目的特定最佳实践。

目录结构

首选 Maven 的标准目录布局(源代码位于 src/main/java 下,测试位于 src/test/java 下)。

BUILD 文件

创建 BUILD 文件时,请遵循以下准则:

  • 每个包含 Java 源代码的目录使用一个 BUILD 文件,因为这可以提高构建性能。

  • 每个 BUILD 文件都应包含一个 java_library 规则,如下所示:

    java_library(
        name = "directory-name",
        srcs = glob(["*.java"]),
        deps = [...],
    )
    
  • 库的名称应为包含 BUILD 文件的目录的名称。这会缩短库的标签,即使用 "//package"而不是"//package:package"

  • 源代码应为目录中所有 Java 文件的非递归 glob of 。

  • 测试应位于 src/test 下的匹配目录中,并依赖于此库。

为高级 Java 构建创建新规则

注意:创建新规则适用于高级构建和测试场景。开始使用 Bazel 时,您不需要创建新规则。

以下模块、配置片段和提供程序将帮助您 在构建 Java 项目时扩展 Bazel 的功能

配置 Java 工具链

Bazel 使用两种类型的 Java 工具链:

  • 执行:用于执行和测试 Java 二进制文件,通过 --java_runtime_version 标志进行控制
  • 编译:用于编译 Java 源代码,通过 --java_language_version 标志进行控制

配置其他执行工具链

执行工具链是 JVM(本地或来自代码库),其中包含有关其版本、操作系统和 CPU 架构的一些其他信息。

可以使用模块扩展中的 local_java_repositoryremote_java_repository 代码库规则添加 Java 执行工具链。添加该规则后,可以使用标志来使用 JVM。如果为同一操作系统和 CPU 架构提供了多个定义,则系统会使用第一个定义。

MODULE.bazel 中本地 JVM 的配置示例:

custom_jdk = use_extension("@rules_java//java:extensions.bzl", "java_repository")

custom_jdk.local(
  name = "additionaljdk",          # Can be used with --java_runtime_version=additionaljdk, --java_runtime_version=11 or --java_runtime_version=additionaljdk_11
  version = "11",                  # Optional, if not set it is autodetected
  java_home = "/usr/lib/jdk-15/",  # Path to directory containing bin/java
)
use_repo(custom_jdk, "additionaljdk")
register_toolchains("@additionaljdk//:all")

远程 JVM 的配置示例:

custom_jdk = use_extension("@rules_java//java:extensions.bzl", "java_repository")

custom_jdk.remote(
  name = "openjdk_canary_linux_arm",
  prefix = "openjdk_canary", # Can be used with --java_runtime_version=openjdk_canary_11
  version = "11",            # or --java_runtime_version=11
  target_compatible_with = [ # Specifies constraints this JVM is compatible with
    "@platforms//cpu:arm",
    "@platforms//os:linux",
  ],
  urls = ...,               # Other parameters are from http_repository rule.
  sha256 = ...,
  strip_prefix = ...
)
use_repo(custom_jdk, "openjdk_canary_linux_arm", "openjdk_canary_linux_arm_toolchain_config_repo")

register_toolchains("@openjdk_canary_linux_arm_toolchain_config_repo//:all")

配置其他编译工具链

编译工具链由 JDK 和 Bazel 在编译期间使用的多个工具组成,这些工具提供其他功能,例如:Error Prone、严格的 Java 依赖项、标头编译、Android 脱糖、覆盖率插桩和 IDE 的 genclass 处理。

JavaBuilder 是 Bazel 捆绑的工具,用于执行编译并提供上述功能。实际编译由 JDK 的内部编译器执行。用于编译的 JDK 由工具链的 java_runtime 属性指定。

Bazel 会替换一些 JDK 内部组件。如果 JDK 版本 > 9,则使用 JDK 的标志 --patch_module 修补 java.compilerjdk.compiler 模块。如果 JDK 版本为 8,则使用 -Xbootclasspath 标志修补 Java 编译器。

VanillaJavaBuilder 是 JavaBuilder 的第二种实现,它不会修改 JDK 的内部编译器,也没有任何其他功能。任何内置工具链都不会使用 VanillaJavaBuilder。

除了 JavaBuilder 之外,Bazel 在编译期间还会使用其他几个工具。

`ijar` 工具会处理 `jar` 文件,以移除除调用 签名之外的所有内容。生成的 jar 称为标头 jar。它们用于提高编译增量,方法是仅在函数正文发生更改时重新编译下游依赖项。

singlejar 工具会将多个 jar 文件打包到一个文件中。

genclass 工具会对 Java 编译的输出进行后处理,并生成一个 jar,其中仅包含由注解处理器生成的源代码的类文件。

JacocoRunner 工具会在检测到的文件上运行 Jacoco,并以 LCOV 格式输出结果。

TestRunner 工具会在受控环境中执行 JUnit 4 测试。

您可以通过将 default_java_toolchain 宏添加到 BUILD 文件并注册该宏来重新配置编译,方法是将 register_toolchains 规则添加到 MODULE.bazel 文件,或使用 --extra_toolchains 标志。

仅当 source_version 属性与 --java_language_version 标志指定的值匹配时,才会使用该工具链。

工具链配置示例:

load(
  "@rules_java//toolchains:default_java_toolchain.bzl",
  "default_java_toolchain", "DEFAULT_TOOLCHAIN_CONFIGURATION", "BASE_JDK9_JVM_OPTS", "DEFAULT_JAVACOPTS"
)

default_java_toolchain(
  name = "repository_default_toolchain",
  configuration = DEFAULT_TOOLCHAIN_CONFIGURATION,        # One of predefined configurations
                                                          # Other parameters are from java_toolchain rule:
  java_runtime = "@rules_java//toolchains:remote_jdk11", # JDK to use for compilation and toolchain's tools execution
  jvm_opts = BASE_JDK9_JVM_OPTS + ["--enable_preview"],   # Additional JDK options
  javacopts = DEFAULT_JAVACOPTS + ["--enable_preview"],   # Additional javac options
  source_version = "9",
)

可以使用 --extra_toolchains=//:repository_default_toolchain_definition 或通过将 register_toolchains("//:repository_default_toolchain_definition") 添加到工作区来使用该工具链。

预定义配置:

  • DEFAULT_TOOLCHAIN_CONFIGURATION:所有功能,支持 JDK 版本 >= 9
  • VANILLA_TOOLCHAIN_CONFIGURATION:没有其他功能,支持任意供应商的 JDK。
  • PREBUILT_TOOLCHAIN_CONFIGURATION:与默认配置相同,但仅使用预构建工具(ijarsinglejar
  • NONPREBUILT_TOOLCHAIN_CONFIGURATION:与默认配置相同,但所有工具都是从源代码构建的(这在具有不同 libc 的操作系统上可能很有用)

配置 JVM 和 Java 编译器标志

您可以使用标志或 default_java_toolchain 属性配置 JVM 和 javac 标志。

相关标志包括 --jvmopt--host_jvmopt--javacopt--host_javacopt

相关 default_java_toolchain 属性包括 javacoptsjvm_optsjavabuilder_jvm_optsturbine_jvm_opts

软件包专用 Java 编译器标志配置

您可以使用 default_java_toolchainpackage_configuration 属性为特定源文件配置不同的 Java 编译器标志。 请参阅以下示例。

load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain")

# This is a convenience macro that inherits values from Bazel's default java_toolchain
default_java_toolchain(
    name = "toolchain",
    package_configuration = [
        ":error_prone",
    ],
    visibility = ["//visibility:public"],
)

# This associates a set of javac flags with a set of packages
java_package_configuration(
    name = "error_prone",
    javacopts = [
        "-Xep:MissingOverride:ERROR",
    ],
    packages = ["error_prone_packages"],
)

# This is a regular package_group, which is used to specify a set of packages to apply flags to
package_group(
    name = "error_prone_packages",
    packages = [
        "//foo/...",
        "-//foo/bar/...", # this is an exclusion
    ],
)

单个代码库中的多个 Java 源代码版本

Bazel 仅支持在构建中编译单个版本的 Java 源代码。这意味着,在构建 Java 测试或应用时,所有 依赖项都是针对同一 Java 版本构建的。

不过,可以使用不同的标志执行单独的构建。

为了更轻松地使用不同的标志,您可以将特定版本的标志集与 .bazelrc 配置分组:

build:java8 --java_language_version=8
build:java8 --java_runtime_version=local_jdk_8
build:java11 --java_language_version=11
build:java11 --java_runtime_version=remotejdk_11

这些配置可以与 --config 标志搭配使用,例如 bazel test --config=java11 //:java11_test