Este tutorial aborda os conceitos básicos da criação de aplicativos Java com o Bazel. Você vai configurar seu espaço de trabalho e criar um projeto Java simples que
ilustra os principais conceitos do Bazel, como destinos e arquivos BUILD
.
Tempo estimado de conclusão: 30 minutos.
Conteúdo
Neste tutorial, você vai aprender a:
- Criar um destino
- Visualizar as dependências do projeto
- Dividir o projeto em vários destinos e pacotes
- Controlar a visibilidade desejada nos pacotes
- Fazer referência a destinos por meio de rótulos
- Implantar um destino
Antes de começar
Instalar o Bazel
Para se preparar para o tutorial, primeiro instale o Bazel se ele ainda não estiver instalado.
Instalar o JDK
Instale o Java JDK. A versão preferencial é a 11. As versões entre a 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çadas" > "Variáveis de ambiente..." .
- Na lista "Variáveis do usuário" (na parte superior), clique em "Nova...".
- No campo "Nome da variável", insira
JAVA_HOME
. - Clique em "Browse Directory...".
- Navegue até o diretório 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 amostra do repositório GitHub do Bazel:
git clone https://github.com/bazelbuild/examples
O projeto de exemplo para este tutorial está no diretório examples/java-tutorial
e está estruturado da seguinte maneira:
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, você precisa configurar o espaço de trabalho dele. Um espaço de trabalho é um diretório que contém os arquivos de origem do seu projeto e as saídas de build do Bazel. Ela 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 reside 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 (link em inglês). Você vai aprender sobre pacotes posteriormente 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 espaço de trabalho. Os arquivos que residem em diferentes espaços de trabalho são independentes entre si, a menos que sejam vinculados, o que está além 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 ou bibliotecas executáveis. 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"]),
)
Em nosso exemplo, o destino ProjectRunner
instancia a regra java_binary
integrada
do Bazel. A regra diz ao Bazel para criar um arquivo .jar
e um script de shell do wrapper (ambos nomeados de acordo com o destino).
Os atributos no destino declaram explicitamente as dependências e opções dele.
O atributo name
é obrigatório, mas muitos são opcionais. Por exemplo, no destino da 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. Talvez você tenha notado que nosso exemplo
usa glob para transmitir um conjunto de arquivos de origem para o Bazel
em vez de listá-los um a um.
Criar o projeto
Para criar seu 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 (nesse caso, a própria raiz), e ProjectRunner
é o nome de destino no arquivo BUILD
. Você aprenderá
mais sobre os 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 saídas de
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.
Teste o binário recém-criado:
bazel-bin/ProjectRunner
Analisar o gráfico de dependências
O Bazel exige que as dependências de build sejam explicitamente declaradas em arquivos BUILD. Ele usa essas instruções para criar o gráfico de dependências do projeto, o que permite builds incrementais precisos.
Para visualizar as dependências do projeto de exemplo, gere uma representação em texto do gráfico de dependências executando este comando na raiz do espaço de trabalho:
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
O comando acima diz ao Bazel para 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.
Depois, cole o texto no GraphViz.
Como você pode notar, o projeto tem um único destino que cria dois arquivos de origem sem outras dependências:
Depois de configurar o espaço de trabalho, criar o projeto e examinar as dependências, adicione um pouco de complexidade.
Refinar o build do Bazel
Embora um único destino seja suficiente para projetos pequenos, você pode dividir projetos maiores em vários destinos e pacotes para permitir builds incrementais rápidos (ou seja, apenas recriar o que mudou) e acelerar os builds criando várias partes de um projeto de uma só vez.
Especificar vários destinos de build
É possível dividir o build do projeto de exemplo em dois destinos. 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, em seguida, 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
Teste o binário recém-criado:
bazel-bin/ProjectRunner
Se você modificar ProjectRunner.java
e recompilar o projeto, o Bazel só
recompilará esse arquivo.
Analisando o gráfico de dependências, observe que ProjectRunner
depende das
mesmas entradas que antes, mas a estrutura do build é diferente:
Você criou o projeto com dois destinos. O destino ProjectRunner
cria
dois arquivos de origem e depende de um outro destino (:greeter
), que cria
um arquivo de origem extra.
Usar vários pacotes
Agora, vamos dividir o projeto em vários pacotes. Se você observar o diretório
src/main/java/com/example/cmdline
, vai notar que ele também contém
um arquivo BUILD
, além de 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 //
(por isso, o rótulo de destino //:greeter
). O Bazel sabe isso pelo atributo deps
.
Confira o gráfico de dependências:
No entanto, para que a criação seja bem-sucedida, é necessário conceder explicitamente a visibilidade do destino runner
em //src/main/java/com/example/cmdline/BUILD
para destinos em
//BUILD
usando o atributo visibility
. Isso ocorre porque, por padrão, os destinos
são visíveis apenas para outros destinos no mesmo arquivo BUILD
. O Bazel usa a visibilidade
de 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 você pode criar o novo pacote executando o comando a seguir 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
Teste o binário recém-criado:
./bazel-bin/src/main/java/com/example/cmdline/runner
Você modificou o projeto para criar como dois pacotes, cada um contendo um destino, além de entender as dependências entre eles.
Usar rótulos para fazer referência a destinos
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 deles é a seguinte:
//path/to/package:target-name
Se o destino for um destino de regra, path/to/package
será o caminho para o
diretório que contém o arquivo BUILD
, e target-name
será o que você nomeou de
destino no arquivo BUILD
(o atributo name
). Se o destino for um destino
de arquivo, 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 mencionar destinos na raiz do repositório, o caminho do pacote está vazio,
basta usar //:target-name
. Ao referenciar destinos no mesmo arquivo BUILD
, é possível até ignorar 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
,
era necessário 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 do ambiente de execução. Isso permite que você execute o binário fora do seu ambiente de desenvolvimento.
Como se lembrar, a regra de build java_binary
produz um .jar
e um script de 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 é o seguinte:
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
Como você pode notar, runner.jar
contém Runner.class
, mas não a dependência Greeting.class
. O script runner
gerado pelo Bazel adiciona greeter.jar
ao caminho de classe. Assim, se você deixá-lo assim, ele será executado localmente, mas
não será independente em outra máquina. Felizmente, a regra java_binary
permite criar um binário autônomo e implantável. Para criá-lo, anexe
_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 seu ambiente de desenvolvimento, já que contém as dependências necessárias de
ambiente de execução. Confira o conteúdo desse JAR autônomo 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 a execução:
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
Leia mais
Confira mais detalhes em:
rules_jvm_external para que regras gerenciem as dependências transitivas do Maven.
Dependências externas para saber mais sobre como trabalhar com repositórios locais e remotos.
Consulte 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.
Consulte o tutorial do aplicativo Android e o tutorial do aplicativo iOS para começar a criar aplicativos para dispositivos móveis Android e iOS com o Bazel.
Divirta-se!