Anote a data: o BazelCon 2023 acontecerá de 24 a 25 de outubro no Google Munique. As inscrições já estão abertas. Saiba mais

Guia de início rápido de consultas

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.

Diagrama mostrando uma relação de café a chef e os pratos: pizza, mac e queijo, que divide os ingredientes separados: queijo, tomate, massa e macarrão.

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

O mesmo gráfico do primeiro, exceto que há um spoke derivado do alvo do chef, com smoothie, que leva à banana e ao morango.

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

Caminho de saída de café para chef, pizza, macarrão e queijo para queijo

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.