Este tutorial aborda os conceitos básicos da criação de aplicativos Java com
Bazel. Você vai configurar seu espaço de trabalho e criar um projeto simples em Java que
ilustra conceitos importantes do Bazel, como destinos e arquivos BUILD
.
Tempo estimado para conclusão: 30 minutos.
O que você vai aprender
Neste tutorial, você vai aprender a:
- Criar um destino
- Visualizar as dependências do projeto
- Dividir o projeto em várias metas e pacotes
- Controlar a visibilidade do destino em todos os pacotes
- Referenciar destinos usando rótulos
- Implantar um destino
Antes de começar
Instalar o Bazel
Para se preparar para o tutorial, primeiro instale o Bazel, caso ainda não tenha feito isso.
Instalar o JDK
Instale o Java JDK. A versão 11 é a preferida, mas as versões entre 8 e 15 são compatíveis.
Defina a variável de ambiente JAVA_HOME para apontar para o JDK.
No Linux/macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
No Windows:
- Abra o Painel de controle.
- Acesse "Sistema e segurança" > "Sistema" > "Configurações avançadas do sistema" > guia "Avançado" > "Variáveis de ambiente..." .
- Na lista "Variáveis do usuário" (a de cima), clique em "Nova...".
- No campo "Nome da variável", insira
JAVA_HOME
. - Clique em "Procurar diretório...".
- Navegue até o diretório do JDK (por exemplo,
C:\Program Files\Java\jdk1.8.0_152
). - Clique em "OK" em todas as janelas de diálogo.
Acessar o projeto de exemplo
Recupere o projeto de exemplo do repositório do GitHub do Bazel:
git clone https://github.com/bazelbuild/examples
O projeto de exemplo deste tutorial está no diretório examples/java-tutorial
e tem a seguinte estrutura:
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
Criação com o Bazel
Configurar o espaço de trabalho
Antes de criar um projeto, é necessário configurar o espaço de trabalho dele. Um espaço de trabalho é um diretório que contém os arquivos de origem do projeto e as saídas de build do Bazel. Ele também contém arquivos que o Bazel reconhece como especiais:
O arquivo
WORKSPACE
, que identifica o diretório e o conteúdo dele como um espaço de trabalho do Bazel e fica na raiz da estrutura de diretórios do projeto.Um ou mais arquivos
BUILD
, que informam ao Bazel como criar diferentes partes do projeto. Um diretório no espaço de trabalho que contém um arquivoBUILD
é um pacote. Você vai aprender sobre pacotes mais adiante neste tutorial.)
Para designar um diretório como um espaço de trabalho do Bazel, crie um arquivo vazio chamado WORKSPACE
nesse diretório.
Quando o Bazel cria o projeto, todas as entradas e dependências precisam estar no mesmo workspace. Os arquivos em diferentes espaços de trabalho são independentes uns dos outros, a menos que estejam vinculados, o que está fora do escopo deste tutorial.
Entender o arquivo BUILD
Um arquivo BUILD
contém vários tipos diferentes de instruções para o Bazel.
O tipo mais importante é a regra de build, que informa ao Bazel como criar as saídas desejadas, como binários executáveis ou bibliotecas. Cada instância
de uma regra de build no arquivo BUILD
é chamada de destino e aponta para um
conjunto específico de arquivos de origem e dependências. Um destino também pode apontar para outros destinos.
Confira o arquivo java-tutorial/BUILD
:
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
No nosso exemplo, o destino ProjectRunner
cria uma instância da regra java_binary
integrada do Bazel. A regra informa ao Bazel para
criar um arquivo .jar
e um script shell de wrapper (ambos nomeados de acordo com o destino).
Os atributos no destino declaram explicitamente as dependências e opções.
Embora o atributo name
seja obrigatório, muitos são opcionais. Por exemplo, na
regra ProjectRunner
, name
é o nome do destino, srcs
especifica
os arquivos de origem que o Bazel usa para criar o destino, e main_class
especifica
a classe que contém o método principal. Você pode ter notado que nosso exemplo usa glob para transmitir um conjunto de arquivos de origem ao Bazel em vez de listá-los um por um.
Criar o projeto
Para criar o projeto de exemplo, navegue até o diretório java-tutorial
e execute:
bazel build //:ProjectRunner
No rótulo de destino, a parte //
é o local do arquivo BUILD
em relação à raiz do espaço de trabalho (neste caso, a própria raiz), e ProjectRunner
é o nome do destino no arquivo BUILD
. Você vai aprender mais sobre rótulos de destino no final deste tutorial.
O Bazel produz uma saída semelhante a esta:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
Parabéns, você acabou de criar seu primeiro destino do Bazel! O Bazel coloca os resultados do build no diretório bazel-bin
na raiz do espaço de trabalho. Navegue pelo conteúdo para ter uma ideia da estrutura de saída do Bazel.
Agora teste o binário recém-criado:
bazel-bin/ProjectRunner
Analisar o gráfico de dependência
O Bazel exige que as dependências de build sejam declaradas explicitamente nos arquivos BUILD. O Bazel usa essas instruções para criar o gráfico de dependência do projeto, que permite builds incrementais precisos.
Para visualizar as dependências do projeto de exemplo, gere uma representação de texto do gráfico de dependência executando este comando na raiz do espaço de trabalho:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
O comando acima instrui o Bazel a procurar todas as dependências do destino
//:ProjectRunner
(excluindo dependências implícitas e de host) e formatar a
saída como um gráfico.
Em seguida, cole o texto no GraphViz.
Como você pode ver, o projeto tem um único destino que cria dois arquivos de origem sem dependências adicionais:
Depois de configurar seu espaço de trabalho, criar o projeto e examinar as dependências dele, você poderá adicionar um pouco de complexidade.
Refinar sua build do Bazel
Embora uma única meta seja suficiente para projetos pequenos, talvez seja melhor dividir projetos maiores em várias metas e pacotes para permitir builds incrementais rápidos (ou seja, reconstruir apenas o que mudou) e acelerar os builds criando várias partes de um projeto de uma só vez.
Especificar várias metas de build
É possível dividir a build do projeto de exemplo em duas metas. Substitua o conteúdo do arquivo java-tutorial/BUILD
pelo seguinte:
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
Com essa configuração, o Bazel primeiro cria a biblioteca greeter
e depois o
binário ProjectRunner
. O atributo deps
em java_binary
informa ao Bazel que
a biblioteca greeter
é necessária para criar o binário ProjectRunner
.
Para criar essa nova versão do projeto, execute o seguinte comando:
bazel build //:ProjectRunner
O Bazel produz uma saída semelhante a esta:
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
Agora teste o binário recém-criado:
bazel-bin/ProjectRunner
Se você modificar ProjectRunner.java
e reconstruir o projeto, o Bazel vai recompilar apenas esse arquivo.
Analisando o gráfico de dependências, você pode ver que ProjectRunner
depende das mesmas entradas de antes, mas a estrutura do build é diferente:
Agora você criou o projeto com duas metas. A meta ProjectRunner
cria
dois arquivos de origem e depende de outra meta (:greeter
), que cria
um arquivo de origem adicional.
Usar vários pacotes
Agora, vamos dividir o projeto em vários pacotes. Se você olhar o diretório
src/main/java/com/example/cmdline
, vai ver que ele também contém
um arquivo BUILD
e alguns arquivos de origem. Portanto, para o Bazel, o espaço de trabalho agora contém dois pacotes, //src/main/java/com/example/cmdline
e //
, já que há um arquivo BUILD
na raiz do espaço de trabalho.
Confira o arquivo src/main/java/com/example/cmdline/BUILD
:
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
O destino runner
depende do destino greeter
no pacote //
(portanto, o rótulo do destino //:greeter
). O Bazel sabe disso pelo atributo deps
.
Confira o gráfico de dependência:
No entanto, para que o build seja bem-sucedido, você precisa conceder explicitamente a visibilidade do destino runner
em //src/main/java/com/example/cmdline/BUILD
para destinos em //BUILD
usando o atributo visibility
. Isso acontece porque, por padrão, as metas só ficam visíveis para outras metas no mesmo arquivo BUILD
. O Bazel usa a visibilidade do destino para evitar problemas como bibliotecas que contêm detalhes de implementação vazando para APIs públicas.
Para fazer isso, adicione o atributo visibility
ao destino greeter
em
java-tutorial/BUILD
, conforme mostrado abaixo:
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
Agora, crie o novo pacote executando o seguinte comando na raiz do espaço de trabalho:
bazel build //src/main/java/com/example/cmdline:runner
O Bazel produz uma saída semelhante a esta:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
Agora teste o binário recém-criado:
./bazel-bin/src/main/java/com/example/cmdline/runner
Agora você modificou o projeto para criar dois pacotes, cada um contendo uma meta, e entendeu as dependências entre eles.
Usar rótulos para referenciar metas
Em arquivos BUILD
e na linha de comando, o Bazel usa rótulos de destino para referenciar destinos, por exemplo, //:ProjectRunner
ou //src/main/java/com/example/cmdline:runner
. A sintaxe é a seguinte:
//path/to/package:target-name
Se o destino for uma regra, path/to/package
será o caminho para o
diretório que contém o arquivo BUILD
, e target-name
será o nome do
destino no arquivo BUILD
(o atributo name
). Se o destino for um arquivo
de destino, path/to/package
será o caminho para a raiz do pacote, e
target-name
será o nome do arquivo de destino, incluindo o caminho completo.
Ao fazer referência a destinos na raiz do repositório, o caminho do pacote fica vazio. Basta usar //:target-name
. Ao referenciar destinos no mesmo arquivo BUILD
, você pode até mesmo pular o identificador raiz do espaço de trabalho //
e usar apenas :target-name
.
Por exemplo, para destinos no arquivo java-tutorial/BUILD
, não era necessário especificar um caminho de pacote, já que a raiz do espaço de trabalho é um pacote (//
), e os dois rótulos de destino eram simplesmente //:ProjectRunner
e //:greeter
.
No entanto, para destinos no arquivo //src/main/java/com/example/cmdline/BUILD
, você precisava especificar o caminho completo do pacote de //src/main/java/com/example/cmdline
, e o rótulo de destino era //src/main/java/com/example/cmdline:runner
.
Empacotar um destino Java para implantação
Agora, vamos empacotar um destino Java para implantação criando o binário com todas as dependências de tempo de execução. Isso permite executar o binário fora do seu ambiente de desenvolvimento.
Como você deve se lembrar, a regra de build java_binary
produz um .jar
e um script shell de wrapper. Confira o conteúdo de
runner.jar
usando este comando:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
O conteúdo é:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
Como você pode ver, runner.jar
contém Runner.class
, mas não a dependência Greeting.class
. O script runner
gerado pelo Bazel adiciona greeter.jar
ao classpath. Portanto, se você deixar assim, ele será executado localmente, mas não
será executado de forma independente em outra máquina. Felizmente, a regra java_binary
permite criar um binário independente e implantável. Para criar, adicione
_deploy.jar
ao nome do destino:
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
O Bazel produz uma saída semelhante a esta:
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
Você acabou de criar runner_deploy.jar
, que pode ser executado de forma independente fora do
ambiente de desenvolvimento, já que contém as dependências de tempo de execução
necessárias. Confira o conteúdo desse JAR independente usando o mesmo comando de antes:
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
O conteúdo inclui todas as classes necessárias para executar:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
Leitura adicional
Confira mais detalhes em:
rules_jvm_external para regras que gerenciam dependências transitivas do Maven.
Dependências externas para saber mais sobre como trabalhar com repositórios locais e remotos.
As outras regras para saber mais sobre o Bazel.
O tutorial de build em C++ para começar a criar projetos em C++ com o Bazel.
O tutorial de aplicativos Android e o tutorial de aplicativos iOS para começar a criar aplicativos para dispositivos móveis Android e iOS com o Bazel.
Boa criação!