Java y Bazel

Informar un problema Ver fuente

En esta página, encontrarás recursos que te ayudarán a usar Bazel con proyectos de Java. Vincula a un instructivo, reglas de compilación y otra información específica para compilar proyectos de Java con Bazel.

Cómo trabajar con Bazel

Los siguientes recursos te ayudarán a trabajar con Bazel en proyectos de Java:

Migra a Bazel

Si actualmente compilas tus proyectos de Java con Maven, sigue los pasos de la guía de migración para comenzar a compilar tus proyectos de Maven con Bazel:

Versiones de Java

Hay dos versiones relevantes de Java que se configuran con marcas de configuración:

  • la versión de los archivos fuente en el repositorio
  • la versión del entorno de ejecución de Java que se usa para ejecutar el código y probarlo

Configura la versión del código fuente en tu repositorio

Sin una configuración adicional, Bazel supone que todos los archivos fuente de Java en el repositorio están escritos en una sola versión de Java. Para especificar la versión de las fuentes en el repositorio, agrega build --java_language_version={ver} al archivo .bazelrc, en el que {ver} es, por ejemplo, 11. Los propietarios del repositorio de Bazel deben configurar esta marca para que Bazel y sus usuarios puedan hacer referencia al número de versión de Java del código fuente. Para obtener más detalles, consulta la marca de versión del lenguaje Java.

Cómo configurar la JVM que se usa para ejecutar y probar el código

Bazel usa un JDK para la compilación y otra JVM para ejecutar y probar el código.

De forma predeterminada, Bazel compila el código mediante un JDK y lo descarga, y lo ejecuta y prueba con la JVM instalada en la máquina local. Bazel busca la JVM mediante JAVA_HOME o una ruta de acceso.

Los objetos binarios resultantes son compatibles con la JVM instalada de forma local en las bibliotecas del sistema, lo que significa que los objetos binarios resultantes dependen de lo que esté instalado en la máquina.

Si quieres configurar la JVM que se usa para la ejecución y las pruebas, usa la marca --java_runtime_version. El valor predeterminado es local_jdk.

Pruebas y compilaciones herméticas

Para crear una compilación hermética, puedes usar la marca de línea de comandos --java_runtime_version=remotejdk_11. El código se compila para, se ejecuta y se prueba en la JVM descargada desde un repositorio remoto. Para obtener más detalles, consulta la marca de la versión del entorno de ejecución de Java.

Configuración de la compilación y ejecución de herramientas de compilación en Java

Hay un segundo par de JDK y JVM que se usa para compilar y ejecutar herramientas, que se usan en el proceso de compilación, pero no están en los resultados de compilación. Ese JDK y JVM se controlan mediante --tool_java_language_version y --tool_java_runtime_version. Los valores predeterminados son 11 y remotejdk_11, respectivamente.

Cómo compilar con el JDK instalado de forma local

De forma predeterminada, Bazel compila con un JDK remoto, ya que anula los elementos internos del JDK. Las cadenas de herramientas de compilación que usan el JDK instalado de forma local están configuradas, pero no se usan.

Si quieres compilar con el JDK instalado de forma local, usa las cadenas de herramientas de compilación para el JDK local, usa la marca adicional --extra_toolchains=@local_jdk//:all. Sin embargo, ten en cuenta que es posible que no funcione en el JDK de proveedores arbitrarios.

Para obtener más detalles, consulta Cómo configurar cadenas de herramientas de Java.

Prácticas recomendadas

Además de las prácticas recomendadas generales de Bazel, a continuación se incluyen las prácticas recomendadas específicas para proyectos de Java.

Estructura del directorio

Prefiere el diseño del directorio estándar de Maven (fuentes en src/main/java, pruebas en src/test/java).

Archivos BUILD

Sigue estos lineamientos cuando crees tus archivos BUILD:

  • Usa un archivo BUILD por directorio que contenga fuentes de Java, ya que esto mejora el rendimiento de la compilación.

  • Cada archivo BUILD debe contener una regla java_library como la siguiente:

    java_library(
        name = "directory-name",
        srcs = glob(["*.java"]),
        deps = [...],
    )
    
  • El nombre de la biblioteca debe ser el nombre del directorio que contiene el archivo BUILD. Esto hace que la etiqueta de la biblioteca sea más corta, es decir, usa "//package" en lugar de "//package:package".

  • Las fuentes deben ser un glob no recurrente de todos los archivos Java del directorio.

  • Las pruebas deben estar en un directorio coincidente en src/test y depender de esta biblioteca.

Crea reglas nuevas para compilaciones avanzadas de Java

Nota: La creación de reglas nuevas está destinada a situaciones avanzadas de compilación y prueba. No lo necesitas cuando comienzas a usar Bazel.

Los siguientes módulos, fragmentos de configuración y proveedores te ayudarán a extender las capacidades de Bazel cuando compiles tus proyectos de Java:

Configura las cadenas de herramientas de Java

Bazel usa dos tipos de cadenas de herramientas de Java: - ejecución, que se usa para ejecutar y probar objetos binarios de Java, controlada con la marca --java_runtime_version - compilación, que se usa para compilar fuentes de Java y controlada con la marca --java_language_version

Configura cadenas de herramientas de ejecución adicionales

La cadena de herramientas de ejecución es la JVM, ya sea local o de un repositorio, con información adicional sobre su versión, sistema operativo y arquitectura de CPU.

Se pueden agregar cadenas de herramientas de ejecución de Java usando reglas local_java_repository o remote_java_repository en el archivo WORKSPACE. Si agregas la regla, la JVM estará disponible mediante una marca. Cuando se proporcionan varias definiciones para el mismo sistema operativo y la misma arquitectura de CPU, se usa la primera.

Ejemplo de configuración de JVM local:

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
)

Ejemplo de configuración de JVM remota:

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

Configura cadenas de herramientas de compilación adicionales

La cadena de herramientas de compilación consta de JDK y varias herramientas que Bazel usa durante la compilación y que proporciona funciones adicionales, como propensa a errores, dependencias estrictas de Java, compilación de encabezados, expansión de sintaxis de Android, instrumentación de cobertura y manejo de genclass para IDE.

JavaBuilder es una herramienta agrupada en Bazel que ejecuta la compilación y proporciona las funciones antes mencionadas. La compilación real se ejecuta con el compilador interno del JDK. El JDK que se usa para la compilación se especifica mediante el atributo java_runtime de la cadena de herramientas.

Bazel anula algunos componentes internos del JDK. En el caso de la versión 9 de JDK superior, se aplicarán parches a los módulos java.compiler y jdk.compiler con la marca --patch_module del JDK. En el caso de la versión 8 de JDK, se aplican parches al compilador de Java con la marca -Xbootclasspath.

VanillaJavaBuilder es una segunda implementación de JavaBuilder, que no modifica el compilador interno del JDK ni tiene ninguna de las funciones adicionales. Ninguna de las cadenas de herramientas integradas usa VanillaJavaBuilder.

Además de JavaBuilder, Bazel usa otras herramientas durante la compilación.

La herramienta ijar procesa los archivos jar para quitar todo, excepto las firmas de llamadas. Los archivos jar resultantes se denominan archivos jar de encabezado. Se usan para mejorar la incrementalidad de la compilación solo con la recompilación de las dependientes descendentes cuando cambia el cuerpo de una función.

La herramienta singlejar agrupa varios archivos jar en uno solo.

La herramienta genclass procesa posteriormente el resultado de una compilación de Java y produce un jar que contiene solo los archivos de clase para las fuentes que generaron los procesadores de anotaciones.

La herramienta JacocoRunner ejecuta Jacoco en archivos instrumentados y genera resultados en formato LCOV.

La herramienta TestRunner ejecuta pruebas JUnit 4 en un entorno controlado.

Para reconfigurar la compilación, puedes agregar la macro default_java_toolchain a un archivo BUILD y registrarla. Para ello, agrega la regla register_toolchains al archivo WORKSPACE o usa la marca --extra_toolchains.

La cadena de herramientas solo se usa cuando el atributo source_version coincide con el valor especificado por la marca --java_language_version.

Ejemplo de configuración de la cadena de herramientas:

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

que se puede usar con --extra_toolchains=//:repository_default_toolchain_definition o agregando register_toolchains("//:repository_default_toolchain_definition") al espacio de trabajo.

Parámetros de configuración predefinidos:

  • DEFAULT_TOOLCHAIN_CONFIGURATION: Todas las funciones, compatible con versiones de JDK >= 9
  • VANILLA_TOOLCHAIN_CONFIGURATION: Sin funciones adicionales, admite JDK de proveedores arbitrarios.
  • PREBUILT_TOOLCHAIN_CONFIGURATION: Igual que el valor predeterminado, pero solo usa herramientas ya compiladas (ijar, singlejar).
  • NONPREBUILT_TOOLCHAIN_CONFIGURATION: Igual que la configuración predeterminada, pero todas las herramientas están compiladas a partir de fuentes (esto puede ser útil en sistemas operativos con libc diferentes)

Cómo configurar marcas del compilador de JVM y Java

Puedes configurar marcas JVM y javac con marcas o mediante atributos default_java_toolchain.

Las marcas relevantes son --jvmopt, --host_jvmopt, --javacopt y --host_javacopt.

Los atributos default_java_toolchain relevantes son javacopts, jvm_opts, javabuilder_jvm_opts y turbine_jvm_opts.

Configuración de las marcas del compilador de Java específicas del paquete

Puedes configurar diferentes marcas del compilador de Java para archivos fuente específicos usando el atributo package_configuration de default_java_toolchain. Consulta el siguiente ejemplo.

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

Varias versiones de código fuente Java en un solo repositorio

Bazel solo admite la compilación de una única versión de fuentes de Java en una compilación. Esto significa que cuando se compila una prueba o una aplicación de Java, todas las dependencias se compilan en la misma versión de Java.

Sin embargo, se pueden ejecutar compilaciones separadas con marcas diferentes.

Para facilitar la tarea de usar marcas diferentes, los conjuntos de marcas de una versión específica se pueden agrupar con los archivos de configuración de .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

Estos archivos de configuración se pueden usar con la marca --config, por ejemplo, bazel test --config=java11 //:java11_test.