Neste tutorial, mostramos como trabalhar com o Bazel para rastrear dependências no código usando um projeto predefinido.
Para detalhes sobre linguagem e sinalização --output
, consulte os manuais de referência de consulta do Bazel e referência de cquery do Bazel. Receba ajuda no ambiente de desenvolvimento integrado digitando bazel help query
ou bazel help cquery
na linha de comando.
Objetivo
Este guia executa um conjunto de consultas básicas que você pode usar para saber mais sobre as dependências de arquivos do seu projeto. Ele é destinado a novos desenvolvedores do Bazel com um conhecimento básico de como os arquivos BUILD
e Bazel funcionam.
Pré-requisitos
Comece instalando o Bazel, caso ainda não tenha feito isso. Este tutorial usa o Git para controle de origem. Portanto, para ter melhores resultados, instale também o Git.
Para visualizar os gráficos de dependência, usamos a ferramenta Graphviz. Você pode fazer o download dela para acompanhar.
Acessar o projeto de amostra
Em seguida, recupere o app de exemplo do repositório de exemplos do Bazel. Para isso, execute o seguinte na ferramenta de linha de comando de sua escolha:
git clone https://github.com/bazelbuild/examples.git
O projeto de amostra deste tutorial está no diretório examples/query-quickstart
.
Como começar
O que são consultas do Bazel?
As consultas ajudam você a aprender sobre uma base de código do Bazel analisando as relações entre os arquivos BUILD
e examinando a saída resultante em busca de informações úteis. Este guia mostra algumas funções básicas de consulta, mas, para mais opções, consulte o guia de consulta. As consultas ajudam você a aprender sobre dependências em projetos de grande escala sem navegar manualmente por arquivos BUILD
.
Para executar uma consulta, abra o terminal da linha de comando e digite:
bazel query 'query_function'
Cenário
Imagine um cenário que investigue a relação entre a Cafe Bazel e o respectivo chef. Este café vende exclusivamente pizza e mac & cheese. Veja abaixo como o projeto está estruturado:
bazelqueryguide
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── customers
│ │ ├── Jenny.java
│ │ ├── Amir.java
│ │ └── BUILD
│ ├── dishes
│ │ ├── Pizza.java
│ │ ├── MacAndCheese.java
│ │ └── BUILD
│ ├── ingredients
│ │ ├── Cheese.java
│ │ ├── Tomatoes.java
│ │ ├── Dough.java
│ │ ├── Macaroni.java
│ │ └── BUILD
│ ├── restaurant
│ │ ├── Cafe.java
│ │ ├── Chef.java
│ │ └── BUILD
│ ├── reviews
│ │ ├── Review.java
│ │ └── BUILD
│ └── Runner.java
└── WORKSPACE
Ao longo deste tutorial, a menos que você receba uma instrução diferente, tente não procurar as informações necessárias nos arquivos BUILD
. Em vez disso, use apenas a função de consulta.
Um projeto consiste em diferentes pacotes que compõem um café. Eles são separados em: restaurant
, ingredients
, dishes
, customers
e reviews
. As regras dentro desses pacotes definem diferentes componentes do Cafe, com várias tags e dependências.
Como executar um build
Esse projeto contém um método principal dentro de Runner.java
que pode ser executado
para mostrar um cardápio do café. Crie o projeto usando o Bazel com o comando
bazel build
e use :
para sinalizar que o destino é chamado de runner
. Consulte nomes de destino para saber como fazer referência a destinos.
Para criar este projeto, cole este comando em um terminal:
bazel build :runner
A saída será semelhante a esta se o build for bem-sucedido.
INFO: Analyzed target //:runner (49 packages loaded, 784 targets configured).
INFO: Found 1 target...
Target //:runner up-to-date:
bazel-bin/runner.jar
bazel-bin/runner
INFO: Elapsed time: 16.593s, Critical Path: 4.32s
INFO: 23 processes: 4 internal, 10 darwin-sandbox, 9 worker.
INFO: Build completed successfully, 23 total actions
Depois de criar, execute o aplicativo colando este comando:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Com isso, você terá uma lista dos itens de menu fornecidos com uma breve descrição.
Explorando destinos
O projeto lista ingredientes e pratos nos próprios pacotes. Para usar uma consulta para visualizar as regras de um pacote, execute o comando bazel query package/…
Neste caso, você pode usar o código para ver os ingredientes e pratos que este café tem. Para isso, execute:
bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/ingredients/...
Se você consultar os destinos do pacote ingredientes, a saída será semelhante a esta:
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
Como encontrar dependências
Quais destinos o executor depende para executar?
Digamos que você queira se aprofundar na estrutura do projeto sem se preocupar com o sistema de arquivos (o que pode ser insustentável para projetos grandes). Quais regras o Cafe Bazel usa?
Se, como neste exemplo, o destino do executor for runner
, descubra as dependências do destino executando o comando:
bazel query --noimplicit_deps "deps(target)"
bazel query --noimplicit_deps "deps(:runner)"
//:runner
//:src/main/java/com/example/Runner.java
//src/main/java/com/example/dishes:MacAndCheese.java
//src/main/java/com/example/dishes:Pizza.java
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:Cheese.java
//src/main/java/com/example/ingredients:Dough.java
//src/main/java/com/example/ingredients:Macaroni.java
//src/main/java/com/example/ingredients:Tomato.java
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
//src/main/java/com/example/restaurant:Cafe.java
//src/main/java/com/example/restaurant:Chef.java
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
Na maioria dos casos, use a função de consulta deps()
para consultar as dependências de saída individuais de um destino específico.
Como visualizar o gráfico de dependências (opcional)
Esta seção descreve como visualizar os caminhos de dependência de uma consulta específica. O Graphviz ajuda a ver o caminho como uma imagem de gráfico acíclico dirigida, em vez de uma lista simplificada. É possível alterar a exibição do gráfico de consultas do Bazel usando várias opções de linha de comando --output
. Consulte Formatos de saída para mais opções.
Comece executando a consulta desejada e adicione a flag --noimplicit_deps
para remover muitas dependências de ferramentas. Depois, siga a consulta com a flag de saída e armazene o gráfico em um arquivo chamado graph.in
para criar uma representação de texto do gráfico.
Para pesquisar todas as dependências do :runner
de destino e formatar a saída como um gráfico:
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in
Isso cria um arquivo chamado graph.in
, que é uma representação de texto do gráfico de build. O Graphviz usa dot
, uma ferramenta que processa texto em uma visualização, para criar um arquivo PNG:
dot -Tpng < graph.in > graph.png
Se você abrir graph.png
, vai aparecer algo assim. O gráfico abaixo foi simplificado para tornar os detalhes essenciais do caminho mais claros neste guia.
Isso ajuda quando você quer ver as saídas das diferentes funções de consulta ao longo deste guia.
Como encontrar dependências reversas
Se, em vez disso, você tiver um destino e quiser analisar o que os outros usam, use uma consulta para examinar quais destinos dependem de uma determinada regra. Isso é chamado de "dependência reversa". Usar rdeps()
pode ser útil ao editar um arquivo em uma base de código com que você não está familiarizado e pode evitar que você corrompa outros arquivos que dependiam dele sem saber.
Por exemplo, você quer fazer algumas edições no ingrediente cheese
. Para evitar causar um problema no Cafe Bazel, você precisa verificar quais pratos dependem de cheese
.
Para ver quais destinos dependem de um destino/pacote específico, use rdeps(universe_scope, target)
. A função de consulta rdeps()
usa pelo menos dois argumentos: um universe_scope
, o diretório relevante, e um target
. O Bazel procura as dependências reversas do destino no universe_scope
fornecido. O operador rdeps()
aceita um terceiro argumento opcional: um literal de número inteiro que especifica o limite superior na profundidade da pesquisa.
Para procurar dependências reversas da cheese
de destino dentro do escopo de todo o projeto "//...", execute o comando:
bazel query "rdeps(universe_scope, target)"
ex) bazel query "rdeps(//... , //src/main/java/com/example/ingredients:cheese)"
//:runner
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
O resultado da consulta mostra que o queijo é usado tanto pela pizza quanto pelo macAndCheese. Que surpresa!
Encontrar destinos com base em tags
Dois clientes entram no Bazel Cafe: Amir e Jenny. Não há nada conhecido sobre eles, exceto seus nomes. Felizmente, os pedidos estão marcados no arquivo BUILD
dos "clientes". Como você pode acessar essa tag?
Os desenvolvedores podem marcar os destinos do Bazel com identificadores diferentes, geralmente para fins de teste. Por exemplo, as tags em testes podem anotar o papel de um teste no processo de depuração e lançamento, especialmente para testes C++ e Python, que não têm qualquer capacidade de anotação no ambiente de execução. O uso de tags e elementos de tamanho oferece flexibilidade na criação de conjuntos de testes com base na política de check-in de uma base de código.
Neste exemplo, as tags são pizza
ou macAndCheese
para representar os itens do menu. Este comando consulta destinos que tenham tags correspondentes ao seu identificador em um determinado pacote.
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
Esta consulta retorna todos os destinos do pacote "customers" que têm a tag "pizza".
Faça um teste
Use esta consulta para saber o que Janaína quer pedir.
Resposta
Mac e queijo
Como adicionar uma nova dependência
O Cafe Bazel expandiu o menu e agora os clientes podem pedir um Smoothie! Esse smoothie específico consiste nos ingredientes Strawberry
e Banana
.
Primeiro, adicione os ingredientes necessários para o smoothie: Strawberry.java
e Banana.java
. Adicione as classes Java vazias.
src/main/java/com/example/ingredients/Strawberry.java
package com.example.ingredients;
public class Strawberry {
}
src/main/java/com/example/ingredients/Banana.java
package com.example.ingredients;
public class Banana {
}
Em seguida, adicione Smoothie.java
ao diretório apropriado: dishes
.
src/main/java/com/example/dishes/Smoothie.java
package com.example.dishes;
public class Smoothie {
public static final String DISH_NAME = "Smoothie";
public static final String DESCRIPTION = "Yummy and Refreshing";
}
Por fim, adicione esses arquivos como regras nos arquivos BUILD
apropriados. Crie uma nova biblioteca Java para cada novo ingrediente, incluindo o nome, a visibilidade pública e o arquivo "src" recém-criado. Ao final, você verá este arquivo BUILD
atualizado:
src/main/java/com/example/ingredients/BUILD
java_library(
name = "cheese",
visibility = ["//visibility:public"],
srcs = ["Cheese.java"],
)
java_library(
name = "dough",
visibility = ["//visibility:public"],
srcs = ["Dough.java"],
)
java_library(
name = "macaroni",
visibility = ["//visibility:public"],
srcs = ["Macaroni.java"],
)
java_library(
name = "tomato",
visibility = ["//visibility:public"],
srcs = ["Tomato.java"],
)
java_library(
name = "strawberry",
visibility = ["//visibility:public"],
srcs = ["Strawberry.java"],
)
java_library(
name = "banana",
visibility = ["//visibility:public"],
srcs = ["Banana.java"],
)
No arquivo BUILD
de pratos, você quer adicionar uma nova regra para Smoothie
. Isso inclui o arquivo Java criado para Smoothie
como um arquivo "src" e as novas regras que você criou para cada ingrediente do smoothie.
src/main/java/com/example/dishes/BUILD
java_library(
name = "macAndCheese",
visibility = ["//visibility:public"],
srcs = ["MacAndCheese.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:macaroni",
],
)
java_library(
name = "pizza",
visibility = ["//visibility:public"],
srcs = ["Pizza.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:dough",
"//src/main/java/com/example/ingredients:tomato",
],
)
java_library(
name = "smoothie",
visibility = ["//visibility:public"],
srcs = ["Smoothie.java"],
deps = [
"//src/main/java/com/example/ingredients:strawberry",
"//src/main/java/com/example/ingredients:banana",
],
)
Por fim, inclua o smoothie como dependência no arquivo BUILD
do Chef.
src/main/java/com/example/restaurant/BUILD
java\_library(
name = "chef",
visibility = ["//visibility:public"],
srcs = [
"Chef.java",
],
deps = [
"//src/main/java/com/example/dishes:macAndCheese",
"//src/main/java/com/example/dishes:pizza",
"//src/main/java/com/example/dishes:smoothie",
],
)
java\_library(
name = "cafe",
visibility = ["//visibility:public"],
srcs = [
"Cafe.java",
],
deps = [
":chef",
],
)
Crie cafe
novamente para confirmar que não há erros. Se o build funcionar, parabéns. Você adicionou uma nova dependência para "Cafe". Caso contrário, fique de olho nos erros de ortografia e na nomenclatura do pacote. Para mais informações sobre como escrever arquivos BUILD
, consulte o Guia de estilo BUILD (link em inglês).
Agora, visualize o novo gráfico de dependências com a adição do Smoothie
para comparar com o anterior. Para esclarecer, nomeie a entrada do gráfico como graph2.in
e graph2.png
.
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png
Observando graph2.png
, observe que Smoothie
não tem dependências compartilhadas com outros pratos, mas é apenas outro alvo do qual a Chef
depende.
somepath() e allpaths()
E se você quiser consultar por que um pacote depende de outro? Exibir um caminho de dependência entre os dois fornece a resposta.
Duas funções podem ajudar você a encontrar caminhos de dependência: somepath()
e allpaths()
. Dado um destino inicial S e um ponto final E, encontre um caminho entre S e E usando somepath(S,E)
.
Explore as diferenças entre essas duas funções observando as relações entre os alvos do "Chef" e do "Cheese". Há diferentes caminhos possíveis para ir de um destino a outro:
- Chef → MacAndCheese → Queijo
- Chef → Pizza → Queijo
somepath()
fornece um único caminho entre as duas opções, enquanto "allpaths()" gera todos os caminhos possíveis.
Usando o Cafe Bazel como exemplo, execute o seguinte:
bazel query "somepath(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/ingredients:cheese
A saída segue o primeiro caminho de Cafe → Chef → MacAndCheese → Cheese. Se, em vez disso, você usar allpaths()
, terá:
bazel query "allpaths(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
A saída de allpaths()
é um pouco mais difícil de ler, porque é uma lista simplificada das dependências. Visualizar este gráfico com o Graphviz deixa a relação mais clara.
Faça um teste
Um dos clientes da Cafe Bazel fez a primeira avaliação do restaurante. A avaliação não tem alguns detalhes, como a identidade do avaliador e o prato que ela está citando. Felizmente, é possível acessar essas informações com o Bazel. O pacote reviews
contém um programa que imprime uma avaliação de um cliente misterioso. Crie e execute com:
bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review
Saindo apenas das consultas do Bazel, tente descobrir quem escreveu a avaliação e o prato que ele descreveu.
Dica
Verifique as tags e as dependências para encontrar informações úteis.
Resposta
Esta avaliação descrevia a pizza, e Amir foi o revisor. Se você analisar quais dependências essa regra tinha usando
bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'
O resultado desse comando revela que Amir é o revisor.
Em seguida, como você sabe que o revisor é Amir, você pode usar a função de consulta para procurar qual tag Amir tem no arquivo `BUILD` para ver qual prato está lá.
O comando bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
informa que Amir é o único cliente que pediu uma pizza e é o revisor que nos dá a resposta.
Conclusão
Parabéns! Agora você executou várias consultas básicas, que pode testar nos seus próprios projetos. Para saber mais sobre a sintaxe da linguagem de consulta, consulte a página de referência de consulta. Quer consultas mais avançadas? O Guia de consulta apresenta uma lista detalhada de outros casos de uso além dos abordados neste guia.