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.

Para detalhes sobre a linguagem e a sinalização --output, consulte os manuais de Referência de consulta do Bazel e da Referência do cquery do Bazel. Para receber ajuda no ambiente de desenvolvimento integrado, digite 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. É destinado a novos desenvolvedores do Bazel com um conhecimento básico sobre como os arquivos BUILD e dele 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 os melhores resultados, instale também o Git.

Para visualizar os gráficos de dependência, usamos a ferramenta Graphviz. Faça o download dela (em inglês) para acompanhar.

Acessar o projeto de exemplo

Em seguida, execute o seguinte comando na ferramenta de linha de comando da sua escolha para recuperar o app de exemplo do repositório de exemplos do Bazel:

git clone https://github.com/bazelbuild/examples.git

O projeto de exemplo 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 arquivos BUILD e examinando a saída resultante em busca de informações úteis. Este guia exibe 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 digite:

bazel query 'query_function'

Cenário

Imagine um cenário que analise a relação entre o Cafe Bazel e o chef. Este café vende exclusivamente pizza e mac and cheese. Dê uma olhada abaixo em 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 você receba uma instrução diferente, tente não procurar nos arquivos BUILD para encontrar as informações necessárias. 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 desses pacotes definem diferentes componentes do café com diversas 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 menu do café. Crie o projeto usando o Bazel com o comando bazel build e use : para sinalizar que o destino é chamado de runner. Consulte os nomes de destinos para saber como fazer referência a destinos.

Para criar esse projeto, cole o seguinte 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

Após a criação, 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 do menu com uma breve descrição.

Explorar destinos

O projeto lista ingredientes e pratos em seus 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 isso para analisar os ingredientes e pratos que este café tem. Para isso, execute o seguinte:

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, o resultado será semelhante a este:

//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 seu projeto sem produzir 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 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ência (opcional)

A seção descreve como visualizar os caminhos de dependência de uma consulta específica. Graphviz ajuda a analisar o caminho como uma imagem de gráfico acíclico direcionada, em vez de uma lista simplificada. É possível mudar 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 sinalização --noimplicit_deps para remover muitas dependências 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 do build. O Graphviz usa a dot , uma ferramenta que processa texto em uma visualização, para criar um PNG:

dot -Tpng < graph.in > graph.png

Se você abrir o arquivo graph.png, vai ver algo assim. O gráfico abaixo foi simplificado para deixar os detalhes essenciais do caminho mais claros neste guia.

Diagrama mostrando uma relação entre café e chef com os pratos: pizza e macarrão e queijo que se divide em ingredientes diferentes: queijo, tomates, massa e macarrão.

Isso é útil 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 quais destinos o 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 que você não conhece e pode evitar que você corrompa outros arquivos que dependiam dele sem saber.

Por exemplo, você quer editar o 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() aceita 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 cheese de destino 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 tanto por pizza quanto por 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 deles estão marcados no arquivo BUILD "clientes". Como você pode 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 para testes de C++ e Python, que não têm a capacidade de anotação em tempo de execução. O uso de tags e elementos de tamanho proporciona 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. Esse comando consulta destinos com tags correspondentes 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 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 ampliou o cardápio e agora os clientes podem pedir um Smoothie. Este 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. 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 for bem-sucedido, parabéns. Você adicionou uma nova dependência para o "Cafe". Caso contrário, procure erros de ortografia e nomes de pacotes. Para mais informações sobre como escrever arquivos BUILD, consulte o Guia de estilo BUILD.

Agora, visualize o novo gráfico de dependências 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

O mesmo gráfico do primeiro, exceto que agora há um raio saindo do chef alvo com smoothie, o que leva a banana e morango

Observando graph2.png, você pode notar que Smoothie não tem dependências compartilhadas com outros pratos, mas é apenas outro destino do qual a 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 S inicial 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 destinos "Chef" e "Cheese". Há diferentes caminhos possíveis para ir de um destino a outro:

  • Chef → MacAndCheese → Queijo
  • Chef → Pizza → Queijo

somepath() oferece 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

Caminho de saída de um café para o chef, passando por pizza, maci, queijo e queijo

A saída de allpaths() é um pouco mais difícil de ler, porque é uma lista simplificada das dependências. Visualizar este gráfico usando o Graphviz torna a relação mais clara de entender.

Faça um teste

Um dos clientes do 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 se refere. É possível acessar essas informações com o Bazel. O pacote reviews contém um programa que mostra a 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 qual prato eles estavam descrevendo.

Dica

Verifique as tags e as dependências para conferir informações úteis.

Resposta

Essa 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, confira a página Referência de consulta. Quer consultas mais avançadas? O Guia de consultas apresenta uma lista detalhada de mais casos de uso do que os abordados neste guia.