Este tutorial aborda como trabalhar com o Bazel para rastrear dependências no código usando um projeto do Bazel pré-criado.
Para detalhes sobre a linguagem e a flag --output
, consulte os manuais Referência da consulta do Bazel e Referência da consulta c do Bazel. Para receber ajuda no seu ambiente de desenvolvimento integrado, digite bazel help query
ou bazel help cquery
na linha de comando.
Objetivo
Este guia apresenta um conjunto de consultas básicas que podem ser usadas para saber mais sobre as dependências de arquivos do projeto. Ele é destinado a novos desenvolvedores do Bazel com conhecimento básico de como os arquivos BUILD
e do Bazel funcionam.
Pré-requisitos
Comece instalando o Bazel, se ainda não tiver feito isso. Este tutorial usa o Git para controle de origem. Para melhores resultados, instale o Git também.
Para visualizar gráficos de dependência, usamos a ferramenta Graphviz, que pode ser baixada para acompanhar.
Fazer o download do projeto de exemplo
Em seguida, extraia o app de exemplo do repositório de exemplos do Bazel executando o seguinte na ferramenta de linha de comando de sua escolha:
git clone https://github.com/bazelbuild/examples.git
O projeto de exemplo deste tutorial está no diretório examples/query-quickstart
.
Primeiros passos
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 arquivos BUILD
e examinando a saída resultante em busca de informações úteis. Este guia mostra uma prévia de 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 pelos arquivos BUILD
.
Para executar uma consulta, abra o terminal da linha de comando e insira:
bazel query 'query_function'
Cenário
Imagine um cenário que investiga a relação entre o Café Bazel e o chef. Este café vende exclusivamente pizza e macarrão com queijo. Confira 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
└── MODULE.bazel
Ao longo deste tutorial, a menos que seja indicado o contrário, tente não procurar nos arquivos BUILD
para encontrar as informações necessárias e 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 nesses pacotes definem diferentes componentes do Cafe com várias tags e dependências.
Como executar um build
Este projeto contém um método principal dentro de Runner.java
que pode ser executado
para imprimir um menu do Café. Crie o projeto usando o Bazel com o comando
bazel build
e use :
para indicar que o destino é chamado runner
. Consulte
nomes de destino para saber como
criar referências a destinos.
Para criar esse projeto, cole este comando em um terminal:
bazel build :runner
A saída vai ficar assim 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 o aplicativo, cole este comando para executá-lo:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Isso gera uma lista dos itens do menu com uma breve descrição.
Como conhecer os destinos
O projeto lista ingredientes e pratos nos próprios pacotes. Para usar uma consulta e conferir as regras de um pacote, execute o comando bazel query package/…
.
Nesse caso, você pode usar isso para conferir os ingredientes e pratos que o Café tem executando:
bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/ingredients/...
Se você consultar os destinos do pacote de 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 são os objetivos do corredor para correr?
Digamos que você queira se aprofundar na estrutura do seu projeto sem precisar mexer no 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 subjacentes 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 conferir as dependências de saída individuais de um destino específico.
Visualizar o gráfico de dependência (opcional)
A seção descreve como visualizar os caminhos de dependência de uma consulta específica. O Graphviz ajuda a visualizar o caminho como uma imagem de gráfico acíclico dirigido, em vez de uma lista simplificada. É possível alterar a exibição do gráfico de consulta do Bazel usando várias opções de linha de comando --output
. Consulte Formatos de saída para conferir as opções.
Comece executando a consulta desejada e adicione a flag --noimplicit_deps
para remover dependências excessivas de ferramentas. Em seguida, 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 PNG:
dot -Tpng < graph.in > graph.png
Se você abrir graph.png
, vai aparecer algo como isso. O gráfico abaixo foi simplificado para deixar os detalhes essenciais do caminho mais claros neste guia.
Isso ajuda quando você quer conferir as saídas das diferentes funções de consulta neste guia.
Como encontrar dependências reversas
Se você tiver um destino que gostaria de analisar e saber quais outros destinos o usam, use uma consulta para examinar quais destinos dependem de uma determinada regra. Isso é chamado de "dependência reversa". O uso de rdeps()
pode ser útil ao editar um arquivo em uma base de código com a qual você não está familiarizado e pode evitar que você quebre outros arquivos que dependem dele sem saber.
Por exemplo, você quer fazer algumas edições no ingrediente cheese
. Para evitar problemas para o Café Bazel, você precisa verificar quais pratos dependem de cheese
.
Para saber quais destinos dependem de um destino/pacote específico, use rdeps(universe_scope, target)
. A função de consulta rdeps()
recebe 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 da profundidade da pesquisa.
Para procurar dependências reversas do alvo cheese
no 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 retorno da consulta mostra que o queijo é usado por pizza e macAndCheese. Que surpresa!
Como encontrar metas com base em tags
Dois clientes entram no Café Bazel: Amir e Jenny. Não se sabe nada sobre eles, exceto os nomes. Felizmente, os pedidos estão marcados no arquivo BUILD
"customers". Como você pode acessar essa tag?
Os desenvolvedores podem marcar destinos do Bazel com identificadores diferentes, muitas vezes 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 em C++ e Python, que não têm capacidade de anotação de execução. O uso de tags e elementos de tamanho oferece flexibilidade na montagem 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. Esse comando consulta destinos com tags que correspondem ao seu identificador em um determinado pacote.
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
Essa consulta retorna todos os destinos no pacote "customers" que têm uma tag "pizza".
Teste seus conhecimentos
Use essa consulta para saber o que Jenny quer pedir.
Resposta
Macarrão com queijo
Como adicionar uma nova dependência
O Café Bazel expandiu o cardápio. Agora os clientes podem pedir um smoothie. Esse smoothie específico consiste nos ingredientes Strawberry
e Banana
.
Primeiro, adicione os ingredientes de que o smoothie depende: 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. O arquivo BUILD
atualizado vai ficar assim:
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 criadas 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, você quer incluir o smoothie como uma 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 a build for bem-sucedida, parabéns! Você adicionou uma nova dependência para o "Cafe". Caso contrário, verifique se há erros de ortografia e nomes de pacotes. Para mais informações sobre como escrever arquivos BUILD
, consulte o Guia de estilo do BUILD.
Agora, visualize o novo gráfico de dependência com a adição do Smoothie
para comparar com o anterior. Para maior clareza, 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
Analisando graph2.png
, você pode ver que Smoothie
não tem dependências compartilhadas com outros pratos, mas é apenas outro destino em que Chef
depende.
somepath() e allpaths()
E se você quiser consultar por que um pacote depende de outro? A exibição de um caminho de dependência entre os dois fornece a resposta.
Duas funções podem ajudar 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)
.
Analise as diferenças entre essas duas funções observando as relações entre os destinos "Chef" e "Cheese". Há diferentes caminhos possíveis para chegar de um destino a outro:
- Chef → MacAndCheese → Cheese
- Chef → Pizza → Queijo
somepath()
fornece um único caminho das 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 você usar allpaths()
, vai receber:
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. A visualização desse gráfico usando o Graphviz torna a relação mais fácil de entender.
Teste seus conhecimentos
Um dos clientes do Café Bazel fez a primeira avaliação do restaurante. Infelizmente, a avaliação não tem alguns detalhes, como a identidade do usuário e o prato a que se refere. 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
Usando apenas as consultas do Bazel, tente descobrir quem escreveu a avaliação e qual prato ela estava descrevendo.
Dica
Verifique as tags e dependências para conferir informações úteis.
Resposta
Esta avaliação descreve a Pizza e Amir foi o autor da avaliação. Se você analisar as dependências dessa regra 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, use a função de consulta para procurar qual tag Amir tem no arquivo "BUILD" e conferir qual prato está lá.
O comando bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
mostra que Amir é o único cliente que pediu uma pizza e é o revisor que nos dá a resposta.
Conclusão
Parabéns! Agora você já executou várias consultas básicas, que podem ser testadas nos seus próprios projetos. Para saber mais sobre a sintaxe da linguagem de consulta, consulte a página de referência da consulta. Quer mais consultas avançadas? O guia de consultas mostra uma lista detalhada de mais casos de uso do que os abordados neste guia.