Reserva la fecha: BazelCon 2023 se celebrará el 24 y 25 de octubre en Google Múnich. ¡Ya comenzó el registro! Más información

Guía de inicio rápido de consultas

En este instructivo, se explica cómo trabajar con Bazel para rastrear las dependencias en tu código mediante un proyecto de Bazel prediseñado.

Para obtener detalles del lenguaje y la marca --output, consulta los manuales de referencia de consulta de Bazel y Referencia de Bazel C. Para obtener ayuda en tu IDE, escribe bazel help query o bazel help cquery en la línea de comandos.

Objetivo

En esta guía, se explica un conjunto de consultas básicas que puedes usar para obtener más información sobre las dependencias de archivos de tu proyecto. Está dirigido a los desarrolladores nuevos de Bazel que tienen conocimientos básicos sobre el funcionamiento de los archivos BUILD y Bazel.

Requisitos previos

Primero, instala Bazel. En este instructivo, se usa Git para el control de código fuente, por lo que, para obtener mejores resultados, también debes instalar Git.

Para visualizar gráficos de dependencias, se usa la herramienta Graphviz, que puedes descargar y seguir.

Obtén el proyecto de muestra

A continuación, recupera la aplicación de muestra del repositorio de ejemplos de Bazel ejecutando lo siguiente en la herramienta de línea de comandos de tu elección:

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

El proyecto de muestra de este instructivo se encuentra en el directorio examples/query-quickstart.

Cómo comenzar

¿Qué son las consultas de Bazel?

Las consultas te ayudan a obtener información sobre una base de código de Bazel mediante el análisis de las relaciones entre los archivos BUILD y el análisis del resultado para obtener información útil. En esta guía, se muestran algunas funciones básicas de consulta. Para obtener más opciones, visita la guía de consultas. Las consultas te ayudan a obtener información sobre las dependencias de proyectos de gran escala sin navegar de forma manual por los archivos BUILD.

Para ejecutar una consulta, abre la terminal de la línea de comandos y escribe lo siguiente:

bazel query 'query_function'

Situación

Imagina una situación que profundiza en la relación entre Cafe Bazel y su respectivo chef. Cafetería que vende exclusivamente pizzas, macarrones con queso. Observa a continuación la estructura del proyecto:

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

A lo largo de este instructivo, a menos que se indique lo contrario, intenta no buscar en los archivos BUILD para encontrar la información que necesitas y, en su lugar, usar solo la función de consulta.

Un proyecto consta de diferentes paquetes que conforman una cafetería. Se separan en restaurant, ingredients, dishes, customers y reviews. Las reglas de estos paquetes definen diferentes componentes de Cafe con varias etiquetas y dependencias.

Ejecuta una compilación

Este proyecto contiene un método principal dentro de Runner.java que puedes ejecutar para imprimir un menú de Cafe. Compila el proyecto mediante Bazel con el comando bazel build y usa : para indicar que el objetivo se llama runner. Consulta Nombres de destino para obtener información sobre cómo hacer referencia a destinos.

Para compilar este proyecto, pega este comando en una terminal:

bazel build :runner

El resultado debería ser similar a este si la compilación tiene éxito.

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

Después de compilarla correctamente, ejecuta este comando para ejecutar la aplicación:

bazel-bin/runner
--------------------- MENU -------------------------

Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner

----------------------------------------------------

Aparecerá una lista de elementos del menú junto con una descripción breve.

Explorar objetivos

El proyecto incluye los ingredientes y los platos en sus propios paquetes. Si quieres usar una consulta para ver las reglas de un paquete, ejecuta el comando bazel query package/…

En este caso, puedes usar esta herramienta para analizar los ingredientes y los platos que tiene este café ejecutando lo siguiente:

bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/ingredients/...

Si consultas los objetivos del paquete de ingredientes, el resultado debería ser el siguiente:

//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

Encuentra dependencias

¿En qué objetivos se ejecuta tu ejecutor?

Supongamos que deseas profundizar en la estructura de tu proyecto sin sondear el sistema de archivos (que puede ser insostenible para proyectos grandes). ¿Qué normas utiliza Cafe Bazel?

Si, como en este ejemplo, el destino de tu ejecutor es runner, ejecuta el siguiente comando para descubrir las dependencias subyacentes del 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

En la mayoría de los casos, usa la función de consulta deps() para ver las dependencias de salida individuales de un objetivo específico.

Visualiza el grafo de dependencia (opcional)

En la sección, se describe cómo puedes visualizar las rutas de dependencia para una consulta específica. Graphviz ayuda a ver la ruta como una imagen de grafo acíclico dirigido en lugar de una lista compactada. Puedes alterar la visualización del grafo de consulta de Bazel mediante varias opciones de línea de comandos de --output. Consulta Formatos de salida para ver las opciones.

Comienza por ejecutar la consulta deseada y agrega la marca --noimplicit_deps para quitar las dependencias excesivas de herramientas. Luego, sigue la consulta con la marca de resultado y almacena el grafo en un archivo llamado graph.in para crear una representación de texto del grafo.

Para buscar todas las dependencias del :runner de destino y formatear el resultado como un gráfico, haz lo siguiente:

bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in

Esto crea un archivo llamado graph.in, que es una representación de texto del grafo de compilación. Graphviz usa dot , una herramienta que procesa texto en una visualización para crear un archivo png:

dot -Tpng < graph.in > graph.png

Si abres graph.png, deberías ver algo como esto. El gráfico a continuación se simplificó para que los detalles esenciales de la ruta sean más claros en esta guía.

Diagrama que muestra una relación de cafetería, chef y platos: pizza, macarrones con queso, que se mezclan con los ingredientes independientes: queso, tomates, masa y macarrones.

Esto es útil cuando quieres ver los resultados de las diferentes funciones de consulta en esta guía.

Encuentra dependencias inversas

En cambio, si tiene un objetivo que desea analizar en qué otros destinos lo usan, puede realizar una consulta para examinar qué objetivos dependen de una regla determinada. Esto se denomina "dependencia inversa". El uso de rdeps() puede resultar útil cuando se edita un archivo en una base de código que no reconoces y puede evitar que rompas otros archivos que no conocías.

Por ejemplo, quieres realizar algunas modificaciones al ingrediente cheese. Para evitar causar un problema en Cafe Bazel, debes verificar qué platos dependen de cheese.

Para ver qué objetivos dependen de un objetivo o paquete en particular, puedes usar rdeps(universe_scope, target). La función de consulta rdeps() toma al menos dos argumentos: un universe_scope (el directorio relevante) y un target. Bazel busca las dependencias inversas del destino dentro del elemento universe_scope proporcionado. El operador rdeps() acepta un tercer argumento opcional: un literal de número entero que especifica el límite superior en la profundidad de la búsqueda.

.

Para buscar dependencias inversas de la cheese de destino dentro del alcance de todo el proyecto “//...”, ejecuta el siguiente 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

El resultado de la consulta muestra que tanto la pizza como el macAndCheese dependen del queso. ¡Qué sorpresa!

Buscar objetivos basados en etiquetas

Dos clientes entran a Bazel Cafe: Amir y Jenny. Solo se conocen sus nombres. Afortunadamente, tienen sus pedidos etiquetados en el archivo BUILD de "clientes". ¿Cómo puede acceder a esta etiqueta?

Los desarrolladores pueden etiquetar los destinos de Bazel con diferentes identificadores, a menudo para realizar pruebas. Por ejemplo, las etiquetas de las pruebas pueden anotar el rol de una prueba en tu proceso de depuración y lanzamiento, especialmente para las pruebas de C++ y Python, que carecen de capacidad de anotación en el tiempo de ejecución. El uso de etiquetas y elementos de tamaño brinda flexibilidad para el conjunto de conjuntos de pruebas en función de la política de registro de una base de código.

En este ejemplo, las etiquetas son pizza o macAndCheese, para representar los elementos de menú. Este comando consulta los destinos que tienen etiquetas que coinciden con su identificador dentro de un paquete determinado.

bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'

Esta consulta muestra todos los objetivos del paquete 'clientes' que tienen la etiqueta "pizza".

Ponte a prueba

Usa esta consulta para obtener información sobre lo que Jenny quiere pedir.

Respuesta

Macarrones con queso

Cómo agregar una dependencia nueva

Cafe Bazel amplió su menú. ¡Los clientes ahora pueden pedir una batido! Este batido específico consta de los ingredientes Strawberry y Banana.

Primero, agrega los ingredientes de los que depende el batido: Strawberry.java y Banana.java. Agrega las clases vacías de Java.

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 {

}

A continuación, agrega Smoothie.java al directorio correspondiente: 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 último, agrega estos archivos como reglas en los archivos BUILD correspondientes. Crea una nueva biblioteca Java para cada ingrediente nuevo, incluidos el nombre, la visibilidad pública y el archivo "src" que acabas de crear. Deberías terminar con este archivo BUILD actualizado:

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"],
)

En el archivo BUILD de platos, quieres agregar una regla nueva para Smoothie. Esto incluye el archivo Java creado para Smoothie como archivo "src" y las nuevas reglas que creaste para cada ingrediente del batido.

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 último, debes incluir el batido como una dependencia en el archivo BUILD de 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",
    ],
)

Vuelve a compilar cafe para confirmar que no haya errores. Si se compiló con éxito, ¡felicitaciones! Agregaste una nueva dependencia para "Cafetería". De lo contrario, presta atención a los errores de ortografía y la denominación de paquetes. Para obtener más información sobre cómo escribir archivos BUILD, consulta la Guía de estilo de COMPILACIÓN.

Ahora, visualiza el nuevo gráfico de dependencias con la adición de Smoothie para comparar con el anterior. Para mayor claridad, asigna a la entrada del gráfico el nombre graph2.in y graph2.png.

bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png

El mismo gráfico del primero, excepto que ahora hay un radio proveniente del objetivo del chef con batido que lleva a una banana y una fresa

Si observas graph2.png, verás que Smoothie no tiene dependencias compartidas con otros platos, pero es solo otro destino en el que se basa Chef.

somepath() y allpaths()

¿Qué sucede si desea consultar por qué un paquete depende de otro? Mostrar una ruta de dependencia entre los dos proporciona la respuesta.

Dos funciones pueden ayudarte a encontrar rutas de dependencia: somepath() y allpaths(). Dado un destino S y un punto final E, encuentra una ruta entre S y E mediante somepath(S,E).

Explore las diferencias entre estas dos funciones observando las relaciones entre los objetivos "Chef" y "Cheese". Hay diferentes rutas posibles para ir de un destino a otro:

  • Chef → MacAndCheese → Queso
  • Chef → Pizza → Queso

somepath() te proporciona una sola ruta de acceso de las dos opciones, mientras que “allpaths()” muestra todas las rutas posibles.

Con Cafe Bazel como ejemplo, ejecuta lo siguiente:

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

El resultado sigue la primera ruta de Cafe → Chef → MacAndCheese → Cheese. Si, en cambio, usas allpaths(), obtendrás lo siguiente:

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

Ruta de acceso del café, del chef a la pizza, el macarrones con queso y el queso

El resultado de allpaths() es un poco más difícil de leer, ya que es una lista plana de las dependencias. Visualizar este gráfico con Graphviz permite que la relación sea más clara.

Ponte a prueba

Uno de los clientes de Cafe Bazel dio su primera opinión sobre el restaurante. Lamentablemente, faltan algunos detalles, como la identidad del revisor y el plato al que hace referencia. Afortunadamente, puedes acceder a esta información con Bazel. El paquete reviews contiene un programa que imprime una opinión de un cliente misterioso. Compila y ejecuta con:

bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review

Si solo deseas hacer consultas en Bazel, intenta averiguar quién escribió la opinión y qué plato describían.

Pista

Revisa las etiquetas y dependencias para obtener información útil.

Respuesta

En esta reseña, se describía Pizza y Amir era el usuario que dejó la opinión. Si observas las dependencias que esta regla tenía mediante bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)', el resultado de este comando revela que Amir es el revisor. Luego, como sabes que el revisor es Amir, puedes usar la función de búsqueda para buscar qué etiqueta tiene en el archivo "BUILD" y ver qué plato incluye. El resultado del comando bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' es que Amir es el único cliente que pidió una pizza y es el usuario que deja la opinión y que nos brinda la respuesta.

Conclusión

¡Felicitaciones! Ya ejecutó varias consultas básicas, que puede probar en sus propios proyectos. Para obtener más información sobre la sintaxis del lenguaje de consulta, visita la página de referencia de consulta. ¿Deseas hacer consultas más avanzadas? En la Guía de consultas, se muestra una lista detallada de más casos prácticos que los que se abordan en esta guía.