Java và Bazel

Báo cáo sự cố Xem nguồn

Trang này chứa các tài nguyên giúp bạn sử dụng Bazel với các dự án Java. Tài liệu này liên kết đến một hướng dẫn, các quy tắc tạo và các thông tin khác dành riêng cho việc xây dựng các dự án Java bằng Bazel.

Làm việc với Bazel

Các tài nguyên sau đây sẽ giúp bạn làm việc với Bazel trong các dự án Java:

Di chuyển đến Bazel

Nếu bạn đang xây dựng dự án Java bằng Maven, hãy làm theo các bước trong hướng dẫn di chuyển để bắt đầu xây dựng các dự án Maven bằng Bazel:

Phiên bản Java

Có 2 phiên bản Java có liên quan được đặt cờ cấu hình:

  • phiên bản của tệp nguồn trong kho lưu trữ
  • phiên bản của thời gian chạy Java được dùng để thực thi mã và kiểm thử

Định cấu hình phiên bản mã nguồn trong kho lưu trữ của bạn

Nếu không cần cấu hình bổ sung, Bazel sẽ giả định tất cả các tệp nguồn Java trong kho lưu trữ đều được viết bằng một phiên bản Java duy nhất. Để chỉ định phiên bản của nguồn trong kho lưu trữ, hãy thêm build --java_language_version={ver} vào tệp .bazelrc, trong đó {ver} là ví dụ 11. Chủ sở hữu kho lưu trữ Bazel nên đặt cờ này để Bazel và người dùng có thể tham chiếu số phiên bản Java của mã nguồn. Để biết thêm thông tin chi tiết, hãy xem bài viết cờ phiên bản ngôn ngữ Java.

Định cấu hình JVM dùng để thực thi và kiểm thử mã

Bazel sử dụng một JDK để biên dịch và một JVM khác để thực thi và kiểm thử mã.

Theo mặc định, Bazel biên dịch mã bằng cách sử dụng JDK mà công cụ này tải xuống, sau đó thực thi và kiểm thử mã bằng JVM được cài đặt trên máy cục bộ. Bazel tìm kiếm JVM bằng cách sử dụng JAVA_HOME hoặc đường dẫn.

Các tệp nhị phân kết quả tương thích với JVM được cài đặt cục bộ trong thư viện hệ thống, nghĩa là các tệp nhị phân thu được phụ thuộc vào nội dung được cài đặt trên máy.

Để định cấu hình JVM dùng cho quá trình thực thi và kiểm thử, hãy sử dụng cờ --java_runtime_version. Giá trị mặc định là local_jdk.

Thử nghiệm và biên dịch ẩn

Để tạo một quá trình biên dịch khép kín, bạn có thể sử dụng cờ dòng lệnh --java_runtime_version=remotejdk_11. Mã này được biên dịch, thực thi và kiểm thử trên JVM được tải xuống từ một kho lưu trữ từ xa. Để biết thêm thông tin chi tiết, hãy xem bài viết cờ phiên bản thời gian chạy Java.

Định cấu hình quá trình biên dịch và thực thi của các công cụ xây dựng trong Java

Có một cặp JDK và JVM thứ hai dùng để tạo và thực thi các công cụ được dùng trong quy trình xây dựng, nhưng không có trong kết quả bản dựng. JDK và JVM đó được kiểm soát bằng --tool_java_language_version--tool_java_runtime_version. Giá trị mặc định lần lượt là 11remotejdk_11.

Biên dịch bằng JDK được cài đặt cục bộ

Theo mặc định, Bazel biên dịch bằng JDK từ xa, vì nó ghi đè nội dung bên trong của JDK. Các chuỗi công cụ biên dịch sử dụng JDK được cài đặt cục bộ đã được định cấu hình, tuy nhiên, không được sử dụng.

Để biên dịch bằng JDK được cài đặt cục bộ, tức là sử dụng chuỗi công cụ biên dịch cho JDK cục bộ, hãy sử dụng cờ bổ sung --extra_toolchains=@local_jdk//:all. Tuy nhiên, xin lưu ý rằng tính năng này có thể không hoạt động trên JDK của các nhà cung cấp tuỳ ý.

Để biết thêm thông tin chi tiết, hãy xem phần định cấu hình chuỗi công cụ Java.

Các phương pháp hay nhất

Ngoài các phương pháp hay nhất chung về Bazel, dưới đây là các phương pháp hay nhất dành riêng cho các dự án Java.

Cấu trúc thư mục

Ưu tiên bố cục thư mục tiêu chuẩn của Maven (các nguồn thuộc src/main/java, kiểm thử trong src/test/java).

XÂY DỰNG tệp

Hãy làm theo các nguyên tắc sau khi tạo tệp BUILD:

  • Sử dụng một tệp BUILD cho mỗi thư mục chứa nguồn Java, vì việc này sẽ cải thiện hiệu suất của bản dựng.

  • Mỗi tệp BUILD phải chứa một quy tắc java_library có dạng như sau:

    java_library(
        name = "directory-name",
        srcs = glob(["*.java"]),
        deps = [...],
    )
    
  • Tên của thư viện phải là tên của thư mục chứa tệp BUILD. Việc này khiến nhãn của thư viện ngắn hơn, sử dụng "//package" thay vì "//package:package".

  • Các nguồn này phải là glob không đệ quy của tất cả tệp Java trong thư mục.

  • Các lượt kiểm thử phải nằm trong một thư mục phù hợp trong src/test và phụ thuộc vào thư viện này.

Tạo quy tắc mới cho bản dựng Java nâng cao

Lưu ý: Việc tạo quy tắc mới là dành cho các trường hợp kiểm thử và xây dựng nâng cao. Bạn không cần thông tin này khi bắt đầu sử dụng Bazel.

Các mô-đun, mảnh cấu hình và nhà cung cấp sau đây sẽ giúp bạn mở rộng khả năng của Bazel khi xây dựng dự án Java:

Định cấu hình chuỗi công cụ Java

Bazel sử dụng 2 loại chuỗi công cụ Java: – thực thi, dùng để thực thi và kiểm thử các tệp nhị phân Java, được kiểm soát bằng cờ --java_runtime_version – biên dịch, dùng để biên dịch các nguồn Java, được kiểm soát bằng cờ --java_language_version

Định cấu hình chuỗi công cụ thực thi bổ sung

Chuỗi công cụ thực thi là JVM, cục bộ hoặc từ một kho lưu trữ, với một số thông tin bổ sung về phiên bản, hệ điều hành và cấu trúc CPU.

Bạn có thể thêm chuỗi công cụ thực thi Java bằng cách sử dụng các quy tắc local_java_repository hoặc remote_java_repository trong tệp WORKSPACE. Việc thêm quy tắc này sẽ khiến JVM có thể sử dụng được bằng cách sử dụng cờ. Khi có nhiều định nghĩa cho cùng một hệ điều hành và cấu trúc CPU, định nghĩa đầu tiên sẽ được sử dụng.

Cấu hình mẫu của JVM cục bộ:

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
)

Cấu hình mẫu của máy ảo Java từ xa:

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

Định cấu hình chuỗi công cụ biên dịch bổ sung

Chuỗi công cụ biên dịch bao gồm JDK và nhiều công cụ mà Bazel sử dụng trong quá trình biên dịch, đồng thời cung cấp thêm các tính năng như: Lỗi dễ gặp, các phần phụ thuộc Java nghiêm ngặt, biên dịch tiêu đề, đơn giản hoá Android, khả năng đo lường mức độ sử dụng và xử lý genclass cho IDE.

JavaBuilder là một công cụ theo gói của Bazel thực thi quá trình biên dịch và cung cấp các tính năng nói trên. Việc biên dịch thực tế được thực thi bằng trình biên dịch nội bộ bởi JDK. JDK dùng để biên dịch được chỉ định bởi thuộc tính java_runtime của chuỗi công cụ.

Bazel ghi đè một số thành phần bên trong của JDK. Trong trường hợp phiên bản JDK > 9, các mô-đun java.compilerjdk.compiler sẽ được vá bằng cờ --patch_module của JDK. Trong trường hợp JDK phiên bản 8, trình biên dịch Java sẽ được vá bằng cờ -Xbootclasspath.

VanillaJavaBuilder là cách triển khai thứ hai của JavaBuilder, không sửa đổi trình biên dịch nội bộ của JDK và không có bất kỳ tính năng bổ sung nào. VanillaJavaBuilder không được chuỗi công cụ tích hợp sẵn nào sử dụng.

Ngoài JavaBuilder, Bazel còn sử dụng một số công cụ khác trong quá trình biên dịch.

Công cụ ijar xử lý các tệp jar để xoá mọi thứ, ngoại trừ chữ ký cuộc gọi. Các vùng chứa kết quả được gọi là nhóm tiêu đề. Các API này được dùng để cải thiện mức độ gia tăng biên dịch bằng cách chỉ biên dịch lại các phần phụ thuộc hạ nguồn khi phần nội dung của hàm thay đổi.

Công cụ singlejar gói nhiều tệp jar thành một tệp duy nhất.

Công cụ genclass xử lý hậu kỳ đầu ra của quá trình biên dịch Java và tạo một jar chỉ chứa các tệp lớp cho các nguồn do trình xử lý chú giải tạo ra.

Công cụ JacocoRunner chạy Jacoco trên các tệp được đo lường và xuất kết quả ở định dạng LCOV.

Công cụ TestRunner thực thi các kiểm thử JUnit 4 trong một môi trường được kiểm soát.

Bạn có thể định cấu hình lại tính năng biên dịch bằng cách thêm macro default_java_toolchain vào tệp BUILD và đăng ký tệp đó bằng cách thêm quy tắc register_toolchains vào tệp WORKSPACE hoặc bằng cách sử dụng cờ --extra_toolchains.

Chuỗi công cụ chỉ được dùng khi thuộc tính source_version khớp với giá trị do cờ --java_language_version chỉ định.

Ví dụ về cấu hình chuỗi công cụ:

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",
)

Bạn có thể dùng --extra_toolchains=//:repository_default_toolchain_definition hoặc thêm register_toolchains("//:repository_default_toolchain_definition") vào không gian làm việc.

Cấu hình định sẵn:

  • DEFAULT_TOOLCHAIN_CONFIGURATION: tất cả tính năng, hỗ trợ các phiên bản JDK >= 9
  • VANILLA_TOOLCHAIN_CONFIGURATION: không có tính năng bổ sung, hỗ trợ JDK của nhà cung cấp tuỳ ý.
  • PREBUILT_TOOLCHAIN_CONFIGURATION: giống như mặc định, nhưng chỉ dùng các công cụ tạo sẵn (ijar, singlejar)
  • NONPREBUILT_TOOLCHAIN_CONFIGURATION: giống như mặc định, nhưng tất cả công cụ đều được tạo từ các nguồn (điều này có thể hữu ích trên hệ điều hành có libc khác nhau)

Định cấu hình cờ trình biên dịch JVM và Java

Bạn có thể định cấu hình cờ JVM và cờ javac bằng cờ hoặc bằng các thuộc tính default_java_toolchain.

Các cờ liên quan là --jvmopt, --host_jvmopt, --javacopt--host_javacopt.

Các thuộc tính default_java_toolchain có liên quan là javacopts, jvm_opts, javabuilder_jvm_optsturbine_jvm_opts.

Cấu hình cờ trình biên dịch Java cụ thể theo gói

Bạn có thể định cấu hình các cờ trình biên dịch Java khác nhau cho các tệp nguồn cụ thể bằng cách sử dụng thuộc tính package_configuration của default_java_toolchain. Vui lòng tham khảo ví dụ dưới đây.

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
    ],
)

Nhiều phiên bản mã nguồn Java trong một kho lưu trữ

Bazel chỉ hỗ trợ biên dịch một phiên bản nguồn Java duy nhất trong một bản dựng. Tức là khi tạo một kiểm thử Java hoặc một ứng dụng, mọi phần phụ thuộc đều được xây dựng dựa trên cùng một phiên bản Java.

Tuy nhiên, bạn có thể thực thi các bản dựng riêng biệt bằng cách sử dụng các cờ khác nhau.

Để thao tác sử dụng nhiều loại cờ dễ dàng hơn, các bộ cờ cho một phiên bản cụ thể có thể được nhóm với cấu hình .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

Bạn có thể sử dụng các cấu hình này với cờ --config, ví dụ: bazel test --config=java11 //:java11_test.