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

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

为高级 Java 构建创建新规则

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

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

配置 Java 工具链

Bazel 使用两种类型的 Java 工具链: - 执行,用于执行和测试 Java 二进制文件,通过 --java_runtime_version 标志控制 - 编译,用于编译 Java 源代码,通过 --java_language_version 标志控制

配置其他执行工具链

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

Java 执行工具链可以使用 WORKSPACE 文件中的 local_java_repositoryremote_java_repository 规则添加。添加规则可使用标志使 JVM 可用。如果针对同一操作系统和 CPU 架构提供了多个定义,系统会使用第一个定义。

本地 JVM 配置示例:

load("@bazel_tools//tools/jdk:local_java_repository.bzl", "local_java_repository")

local_java_repository(
  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
)

远程 JVM 配置示例:

load("@bazel_tools//tools/jdk:remote_java_repository.bzl", "remote_java_repository")

remote_java_repository(
  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 = ...
)

配置其他编译工具链

编译工具链由 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 文件,然后通过向 WORKSPACE 文件添加 register_toolchains 规则或使用 --extra_toolchains 标志来注册该文件。

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

工具链配置示例:

load(
  "@bazel_tools//tools/jdk: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 = "@bazel_tools//tools/jdk: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("@bazel_tools//tools/jdk: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 仅支持在 build 中编译单个版本的 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