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 進行編譯,因為 Bazel 會覆寫 JDK 的內部內部。但已設定使用本機安裝 JDK 的編譯工具鍊,但實際上並未使用。

如要透過本機安裝的 JDK 進行編譯,也就是在本機 JDK 中使用編譯工具鍊,請使用額外的標記 --extra_toolchains=@local_jdk//:all。不過,這可能無法在任意供應商的 JDK 上運作。

詳情請參閱設定 Java 工具鍊

最佳做法

除了一般的 Bazel 最佳做法以外,以下是 Java 專案特有的最佳做法。

目錄結構

偏好 Maven 的標準目錄版面配置 (src/main/java 底下的來源,src/test/java 底下的測試)。

版本檔案

建立 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 時,並不需要這個項目。

下列模組、設定片段和提供者可協助您在建構 Java 專案時擴充 Bazel 的功能

設定 Java 工具鍊

Bazel 使用兩種 Java 工具鍊: - 執行用於執行和測試 Java 二進位檔,以 --java_runtime_version 標記控制 - 編譯用於編譯 Java 來源,以 --java_language_version 旗標控制

設定其他執行工具鍊

執行工具鍊是本機或存放區的 JVM,包含其版本、作業系統和 CPU 架構的額外資訊。

您可以透過 WORKSPACE 檔案中的 local_java_repositoryremote_java_repository 規則新增 Java 執行工具鍊。新增規則後,您就能透過旗標使用 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 的泛型檢測。

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 規則至 WORKSPACE 檔案或使用 --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 僅支援建構中的單一版本 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