Neste tutorial, mostramos como trabalhar com o Bazel para rastrear dependências no seu código usando um projeto predefinido do Bazel.
Para detalhes sobre a linguagem e a sinalização --output
, consulte os manuais de referência de consulta do Bazel e referência de consulta do Bazel. Receba ajuda no seu ambiente de desenvolvimento integrado digitando bazel help query
ou bazel help cquery
na linha de comando.
Objetivo
Este guia orienta você em um conjunto de consultas básicas para saber mais sobre as dependências de arquivos do seu projeto. Ele é destinado a novos desenvolvedores do Bazel com conhecimento básico sobre como os arquivos do Bazel e do BUILD
funcionam.
Pré-requisitos
Para começar, instale o Bazel, caso ainda não tenha feito isso. Neste tutorial, usamos o Git para controle de origem, portanto, para melhores resultados, instale também o Git.
Para visualizar os gráficos de dependência, a ferramenta Graphviz é usada. Você pode fazer o download 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 que preferir:
git clone https://github.com/bazelbuild/examples.git
O projeto de amostra deste tutorial está no diretório examples/query-quickstart
.
Primeiros passos
O que são consultas do Bazel?
As consultas ajudam você a saber mais sobre uma base de código do Bazel analisando as relações entre os arquivos BUILD
e examinando a saída resultante para encontrar informações úteis. Este guia mostra algumas funções básicas de consulta. Para mais opções, consulte o guia de consultas. Com as consultas, você aprende sobre dependências em projetos em grande escala sem navegar manualmente por 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 analisa a relação entre o Café Bazel e o respectivo chef. Este café vende exclusivamente pizzas e macs e queijo. 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
Neste tutorial, a menos que você receba outra instrução, tente não consultar os arquivos BUILD
para encontrar as informações de que precisa 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 desses pacotes definem diferentes componentes do café 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 imprimir um menu do Cafe. 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 a versão for bem-sucedida.
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
Após a criação, execute o aplicativo colando este comando:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Você verá uma lista com os itens do cardápio, além de uma breve descrição.
Como explorar destinos
O projeto lista ingredientes e pratos nos próprios pacotes. Para usar uma consulta e ver as regras de um pacote, execute o comando bazel query package/…
Nesse caso, é possível usá-la para analisar os ingredientes e as opções de café que esse 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 de ingredientes, a saída será semelhante a:
//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 alvos seu executor depende para ser executado?
Digamos que você queira se aprofundar na estrutura do seu projeto sem analisar o sistema de arquivos (que pode ser insustentável em projetos grandes). Quais regras o Café Bazel usa?
Se, como neste exemplo, o destino do executor for runner
, execute este comando para descobrir as dependências do destino:
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 ver dependências de saída individuais de um destino específico.
Visualizar o gráfico de dependências (opcional)
A seção descreve como visualizar os caminhos de dependência de uma consulta específica. Graphviz ajuda a ver o caminho como uma imagem de gráfico acíclico dirigido, não uma lista simplificada. É possível alterar a exibição do gráfico de consulta do Bazel usando várias opções da linha de comando --output
. Consulte Formatos de saída para ver opções.
Comece executando a consulta desejada e adicione a sinalização --noimplicit_deps
para remover o excesso de dependências da ferramenta. Em seguida, siga a consulta com a sinalização 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 destino :runner
e formatar a saída como um gráfico, faça o seguinte:
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in
Isso cria um arquivo chamado graph.in
, que é uma representação em texto do gráfico do 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
, verá algo parecido com isto. O gráfico abaixo foi simplificado para esclarecer os detalhes essenciais do caminho neste guia.
Isso é útil quando você quer ver os resultados das diferentes funções de consulta ao longo deste guia.
Como encontrar dependências inversas
Se, em vez disso, você tiver uma meta e quiser analisar quais outras usam essa segmentação, é possível usar uma consulta para examinar quais destinos dependem de uma determinada regra. Isso é chamado de "dependência inversa". O uso de rdeps()
pode ser útil ao editar um arquivo em uma base de código que você não conhece e pode evitar que você, acidentalmente, descubra outros arquivos que dependiam dele.
Por exemplo, você quer fazer algumas edições no ingrediente cheese
. Para evitar que isso aconteça, você precisa conferir 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()
recebe pelo menos dois argumentos: um universe_scope
(o diretório relevante) e um target
. O Bazel pesquisa 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 do destino 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 é confiável tanto para pizza quanto para macAndCheese. Que surpresa!
Como encontrar destinos com base em tags
Dois clientes entram no Café Bazel: Amir e Jenny. Não há nada conhecido, exceto o nome. Felizmente, os pedidos estão marcados no arquivo BUILD
"clientes". Como é possível acessar essa tag?
Os desenvolvedores podem marcar 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 em testes C++ e Python, que não têm habilidades de anotação de ambiente 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.
Nesse exemplo, as tags são pizza
ou macAndCheese
para representar os itens de menu. Esse comando consulta destinos de tags que correspondam 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-se
Use esta consulta para saber o que a Júlia quer pedir.
Resposta
Mac e queijo
Como adicionar uma nova dependência
O Café Bazel ampliou o cardápio, e os clientes já podem pedir um Smoothie! Esta smoothie específica é composta pelos 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. Este arquivo BUILD
foi 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
dos 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, inclua 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 versão for criada com sucesso, parabéns! Você adicionou uma nova dependência para o "Cafe". Caso contrário, verifique se há erros de ortografia e nomeação de pacotes. Para ver mais informações sobre a criação de 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 comparação 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
Observando graph2.png
, é possível ver que Smoothie
não tem dependências compartilhadas com outros pratos, mas é apenas outro destino em que o 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 você a encontrar caminhos de dependência: somepath()
e allpaths()
. Com um destino inicial S e um endpoint final E, use somepath(S,E)
para encontrar um caminho entre S e E.
Explore as diferenças entre essas duas funções analisando as relações entre os objetivos "Chef" e "Cheese". Há diferentes caminhos possíveis para ir de um destino a outro:
- Chef → MacAndCheese → Queijo
- Chef → Pizza → Queijo
somepath()
fornece um caminho único entre as duas opções, enquanto "allpaths()" retorna 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 Café → Chef → MacAndCheese → Queijo. 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. A visualização desse gráfico usando o Graphviz deixa a relação mais clara.
Teste-se
Um dos clientes do Café Bazel deu a primeira avaliação no restaurante! Faltam alguns detalhes na avaliação, como a identidade do avaliador e o prato que está sendo referenciado. Felizmente, é possível acessar essas informações com o Bazel. O pacote reviews
contém um programa que mostra 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
Somente consultas do Bazel: tente descobrir quem escreveu a avaliação e o prato que estava descrevendo.
Dica
Confira as tags e as dependências para ver informações úteis.
Resposta
Esta avaliação descreveu a pizza, e Amir foi a pessoa que avaliou. Ao analisar as dependências que 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, use a função de consulta para procurar qual tag ela tem no arquivo "BUILD" e ver qual é o prato.
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 avaliador que nos fornece a resposta.
Conclusão
Parabéns! Agora você já executou várias consultas básicas e pode fazer testes nos seus próprios projetos. Saiba mais sobre a sintaxe da linguagem de consulta na página de referência de consulta. Quer consultas mais avançadas? O guia de consultas apresenta uma lista detalhada de mais casos de uso abordados neste guia.