Java e Bazel

Informar um problema Acessar a origem

Nesta página, você encontra recursos para usar o Bazel com projetos Java. Ele tem links para um tutorial, regras de build e outras informações específicas sobre a criação de projetos Java com o Bazel.

Como trabalhar com o Bazel

Use os recursos abaixo para trabalhar com o Bazel em projetos Java:

Como migrar para o Bazel

Se você cria seus projetos Java com o Maven, siga as etapas do guia de migração para começar a criar esses projetos:

Versões do Java

Há duas versões relevantes do Java definidas com sinalizações de configuração:

  • a versão dos arquivos de origem no repositório
  • a versão do ambiente de execução do Java que é usada para executar o código e testá-lo

Configurar a versão do código-fonte no seu repositório

Sem uma configuração extra, o Bazel presume que todos os arquivos de origem Java no repositório são gravados em uma única versão do Java. Para especificar a versão das origens no repositório, adicione build --java_language_version={ver} ao arquivo .bazelrc, em que {ver} é, por exemplo, 11. Os proprietários de repositórios do Bazel precisam definir essa sinalização para que o Bazel e os usuários dele possam referenciar o número da versão do Java do código-fonte. Para mais detalhes, consulte Sinalização de versão da linguagem Java.

Configurar a JVM usada para executar e testar o código

O Bazel usa um JDK para compilação e outra JVM para executar e testar o código.

Por padrão, o Bazel compila o código usando um JDK que ele baixa e executa e testa o código com a JVM instalada na máquina local. O Bazel procura a JVM usando JAVA_HOME ou o caminho.

Os binários resultantes são compatíveis com a JVM instalada localmente em bibliotecas do sistema, o que significa que os binários resultantes dependem do que está instalado na máquina.

Para configurar a JVM usada para execução e teste, use a sinalização --java_runtime_version. O valor padrão é local_jdk.

Teste e compilação herméticos

Para criar uma compilação hermética, use a sinalização de linha de comando --java_runtime_version=remotejdk_11. O código é compilado, executado e testado na JVM transferida por download de um repositório remoto. Para mais detalhes, consulte Sinalização de versão do ambiente de execução Java.

Configurar a compilação e a execução de ferramentas de build em Java

Há um segundo par de JDK e JVM usados para criar e executar ferramentas, que são usadas no processo de compilação, mas não estão nos resultados. Que o JDK e a JVM são controlados usando --tool_java_language_version e --tool_java_runtime_version. Os valores padrão são 11 e remotejdk_11, respectivamente.

Como compilar usando o JDK instalado localmente

Por padrão, o Bazel é compilado usando o JDK remoto, porque substitui os componentes internos do JDK. Os conjuntos de ferramentas de compilação que usam o JDK instalado localmente são configurados, mas não são usados.

Para compilar usando o JDK instalado localmente, ou seja, usar os conjuntos de ferramentas de compilação para o JDK local, use a sinalização extra --extra_toolchains=@local_jdk//:all. No entanto, isso pode não funcionar no JDK de fornecedores arbitrários.

Para mais detalhes, consulte Como configurar conjuntos de ferramentas Java.

Práticas recomendadas

Além das práticas recomendadas gerais do Bazel, confira abaixo as práticas recomendadas específicas para projetos Java.

Estrutura do diretório

Preferir o layout de diretório padrão do Maven (origens em src/main/java, testes em src/test/java).

Arquivos BUILD

Siga estas diretrizes ao criar seus arquivos BUILD:

  • Use um arquivo BUILD por diretório que contenha origens Java, porque isso melhora o desempenho do build.

  • Cada arquivo BUILD precisa conter uma regra java_library parecida com esta:

    java_library(
        name = "directory-name",
        srcs = glob(["*.java"]),
        deps = [...],
    )
    
  • O nome da biblioteca precisa ser o nome do diretório que contém o arquivo BUILD. Isso deixa o rótulo da biblioteca mais curto, ou seja, use "//package" em vez de "//package:package".

  • As origens precisam ser um glob não recursivo de todos os arquivos Java no diretório.

  • Os testes precisam estar em um diretório correspondente em src/test e depender dessa biblioteca.

Como criar novas regras para builds avançados do Java

Observação: a criação de novas regras é destinada a cenários avançados de build e teste. Ela não é necessária para começar a usar o Bazel.

Os módulos, fragmentos de configuração e provedores a seguir ajudarão estender os recursos do Bazel durante a criação dos seus projetos Java:

Como configurar os conjuntos de ferramentas Java

O Bazel usa dois tipos de conjuntos de ferramentas Java: - execução, usada para executar e testar binários Java, controlada com a flag --java_runtime_version - compilação, usada para compilar fontes Java, controlada com a flag --java_language_version

Configurar outros conjuntos de ferramentas de execução

O conjunto de ferramentas de execução é a JVM, seja local ou de um repositório, com algumas informações adicionais sobre a versão, o sistema operacional e a arquitetura da CPU.

Os conjuntos de ferramentas de execução do Java podem ser adicionados usando regras local_java_repository ou remote_java_repository no arquivo WORKSPACE. Adicionar a regra disponibiliza a JVM usando uma flag. Quando são fornecidas várias definições para o mesmo sistema operacional e arquitetura de CPU, a primeira é usada.

Exemplo de configuração 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
)

Exemplo de configuração 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 = ...
)

Como configurar conjuntos de ferramentas de compilação adicionais

O conjunto de ferramentas de compilação é composto por JDK e várias ferramentas que o Bazel usa durante a compilação e que fornece outros recursos, como: propenso a erros, dependências rígidas do Java, compilação de cabeçalho, simplificação do Android, instrumentação de cobertura e processamento de classe generativa para ambientes de desenvolvimento integrado.

O JavaBuilder é uma ferramenta empacotada pelo Bazel que executa a compilação e fornece os recursos mencionados acima. A compilação real é executada usando o compilador interno pelo JDK. O JDK usado para compilação é especificado pelo atributo java_runtime do conjunto de ferramentas.

O Bazel substitui alguns componentes internos do JDK. No caso da versão do JDK posterior à 9, os módulos java.compiler e jdk.compiler recebem patches usando a sinalização --patch_module do JDK. No caso do JDK versão 8, o compilador Java recebe patches usando a sinalização -Xbootclasspath.

VanillaJavaBuilder é uma segunda implementação do JavaBuilder, que não modifica o compilador interno do JDK e não tem nenhum dos recursos adicionais. O VanillaJavaBuilder não é usado por nenhum dos conjuntos de ferramentas integrados.

Além do JavaBuilder, o Bazel usa várias outras ferramentas durante a compilação.

A ferramenta ijar processa arquivos jar para remover tudo, exceto assinaturas de chamada. Os jars resultantes são chamados de jars de cabeçalho. Eles são usados para melhorar a incrementabilidade da compilação apenas recompilando dependentes downstream quando o corpo de uma função muda.

A ferramenta singlejar agrupa vários arquivos jar em um único.

A ferramenta genclass pós-processa a saída de uma compilação Java e produz uma jar contendo apenas os arquivos de classe das origens geradas por processadores de anotações.

A ferramenta JacocoRunner executa o Jacoco em arquivos instrumentados e gera resultados no formato LCOV.

A ferramenta TestRunner executa testes do JUnit 4 em um ambiente controlado.

Você pode reconfigurar a compilação adicionando a macro default_java_toolchain a um arquivo BUILD e registrando-a adicionando a regra register_toolchains ao arquivo WORKSPACE ou usando a flag --extra_toolchains.

O conjunto de ferramentas é usado apenas quando o atributo source_version corresponde ao valor especificado pela sinalização --java_language_version.

Exemplo de configuração do conjunto de ferramentas:

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 pode ser usado usando --extra_toolchains=//:repository_default_toolchain_definition ou adicionando register_toolchains("//:repository_default_toolchain_definition") ao espaço de trabalho.

Configurações predefinidas:

  • DEFAULT_TOOLCHAIN_CONFIGURATION: todos os recursos, com suporte a versões do JDK >= 9.
  • VANILLA_TOOLCHAIN_CONFIGURATION: sem recursos extras, aceita JDKs de fornecedores arbitrários.
  • PREBUILT_TOOLCHAIN_CONFIGURATION: igual ao padrão, mas usa apenas ferramentas pré-criadas (ijar, singlejar)
  • NONPREBUILT_TOOLCHAIN_CONFIGURATION: igual ao padrão, mas todas as ferramentas são criadas a partir de origens. Isso pode ser útil em sistemas operacionais com libcs diferentes.

Como configurar sinalizações do compilador JVM e Java

É possível configurar sinalizações JVM e javac com sinalizações ou com atributos default_java_toolchain.

As sinalizações relevantes são --jvmopt, --host_jvmopt, --javacopt e --host_javacopt.

Os atributos default_java_toolchain relevantes são javacopts, jvm_opts, javabuilder_jvm_opts e turbine_jvm_opts.

Configuração de sinalizações do compilador Java específicas do pacote

É possível configurar diferentes sinalizações do compilador Java para arquivos de origem específicos usando o atributo package_configuration de default_java_toolchain. Consulte o exemplo abaixo.

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

Várias versões do código-fonte Java em um único repositório

O Bazel só é compatível com a compilação de uma única versão de origens Java em um build. Isso significa que, ao criar um teste ou aplicativo Java, todas as dependências são compiladas na mesma versão Java.

No entanto, builds separados podem ser executados usando flags diferentes.

Para facilitar a tarefa de usar sinalizações diferentes, os conjuntos de sinalizações de uma versão específica podem ser agrupados com configurações .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

Essas configurações podem ser usadas com a sinalização --config, por exemplo, bazel test --config=java11 //:java11_test.