Guía de inicio rápido de consultas

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

Para obtener los detalles del idioma y de las marcas --output, consulta los manuales de referencia de consultas de Bazel y Referencia de cquery de Bazel. 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 presenta un conjunto de consultas básicas que puedes usar para obtener más información sobre las dependencias de archivos de tu proyecto. Está dirigida a los desarrolladores nuevos de Bazel con conocimientos básicos sobre el funcionamiento de los archivos de Bazel y BUILD.

Requisitos previos

Comienza instalando Bazel, si aún no lo has hecho. En este instructivo, se usa Git para el control de código fuente, así que, a fin de obtener mejores resultados, instala Git.

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

Obtén el proyecto de muestra

A continuación, recupera la app de ejemplo del repositorio de ejemplos de Bazel ejecutando el siguiente comando en la herramienta de línea de comandos que prefieras:

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

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

Primeros pasos

¿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 resultante para obtener información útil. En esta guía, se ofrecen vistas previas de algunas funciones de consulta básicas, pero puedes consultar la guía de consultas para ver más opciones. Las consultas te ayudan a aprender sobre las dependencias en proyectos a gran escala sin navegar de forma manual por los archivos BUILD.

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

bazel query 'query_function'

Situación

Imagina un escenario que profundiza en la relación entre Cafe Bazel y su respectivo chef. Esta cafetería vende exclusivamente pizza y macarrones con queso. A continuación, echa un vistazo a cómo está estructurado el 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
└── MODULE.bazel

Durante 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, usa la función de consulta.

Un proyecto consiste en diferentes paquetes que conforman una cafetería. Se dividen en: restaurant, ingredients, dishes, customers y reviews. Las reglas de estos paquetes definen diferentes componentes del café con varias etiquetas y dependencias.

Ejecuta una compilación

Este proyecto contiene un método principal dentro de Runner.java que puedes ejecutar para mostrar un menú de Cafe. Compila el proyecto mediante Bazel con el comando bazel build y usa : para indicar que el destino se llama runner. Consulta los nombres de destinos para saber cómo hacer referencia a los objetivos.

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

bazel build :runner

Si la compilación se realiza correctamente, el resultado debería ser similar al siguiente.

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

Una vez que se haya compilado correctamente, pega el siguiente comando para ejecutar la aplicación:

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

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

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

Esto te deja una lista de los elementos del menú que se ofrecen junto con una breve descripción.

Explora los destinos

El proyecto enumera 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 usarlo para buscar los ingredientes y platos que tiene esta cafetería ejecutando lo siguiente:

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

Si consultas por los objetivos del paquete de ingredientes, el resultado debería ser similar al 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

Cómo buscar dependencias

¿En qué objetivos se basa el corredor para ejecutarse?

Digamos que quieres profundizar en la estructura de tu proyecto sin la producción del sistema de archivos (lo cual puede no ser posible en proyectos grandes). ¿Qué reglas usa Cafe Bazel?

Si, como en este ejemplo, el destino de tu ejecutor es runner, descubre las dependencias subyacentes del destino mediante la ejecución del 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

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

Visualiza el gráfico de dependencias (opcional)

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

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

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

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

De esta manera, se creará un archivo llamado graph.in, que es una representación de texto del gráfico 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 siguiente gráfico se simplificó para que los detalles esenciales de la ruta sean más claros en esta guía.

Diagrama que muestra una relación entre el café y el chef de los platos: pizza y macarrones con queso que se convierten en ingredientes separados: queso, tomates, masa y macarrones.

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

Encontrar dependencias inversas

Si, por el contrario, tienes un objetivo y deseas analizar qué lo utilizan otros objetivos, puedes utilizar una consulta para examinar qué objetivos dependen de una regla determinada. Esto se denomina una "dependencia inversa". El uso de rdeps() puede ser útil cuando se edita un archivo en una base de código que no conoces, y puede evitar romper otros archivos que dependían de él sin darte cuenta.

Por ejemplo, si quieres editar el ingrediente cheese. Para evitar un problema con Cafe Bazel, debes verificar qué platos dependen de cheese.

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

Para buscar dependencias inversas del cheese de destino dentro del alcance de todo el proyecto "//...", ejecuta el 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 la pizza y el queso macAndCheese dependen del queso. ¡Qué sorpresa!

Cómo encontrar destinos en función de las etiquetas

Dos clientes entran en el café de Bazel: Amir y Jenny. Solo se sabe de ellos por sus nombres. Por suerte, tienen sus pedidos etiquetados en el archivo BUILD de los “clientes”. ¿Cómo puedes acceder a esta etiqueta?

Los desarrolladores pueden etiquetar destinos de Bazel con diferentes identificadores, a menudo para realizar pruebas. Por ejemplo, las etiquetas en las pruebas pueden anotar la función 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 tiempo de ejecución. El uso de etiquetas y elementos de tamaño brinda flexibilidad en el ensamblaje de conjuntos de pruebas en torno a 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 destinos que tienen etiquetas que coinciden con tu 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 'customers' que tienen la etiqueta "pizza".

Ponte a prueba

Usa esta consulta para saber qué quiere pedir Jenny.

Respuesta

Macarrones con queso

Cómo agregar una dependencia nueva

Cafe Bazel expandió su menú: ¡ahora los clientes pueden pedir un 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 de Java vacías.

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, incluido su nombre, visibilidad pública y su nuevo archivo “src”. 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 los platos, quieres agregar una regla nueva para Smoothie. De esta manera, se incluirá el archivo Java creado para Smoothie como archivo "src" y las reglas nuevas 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, incluye 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 compila correctamente, ¡felicitaciones! Agregaste una nueva dependencia para "Cafetería". De lo contrario, presta atención a los errores ortográficos y al nombre de los 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 compararlo con el anterior. Para mayor claridad, asigna el nombre graph2.in y graph2.png a la entrada del gráfico.

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 que sale del objetivo del chef con un batido que lleva a un plátano y una fresa.

Si observas graph2.png, puedes ver 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 quieres consultar por qué un paquete depende de otro paquete? Mostrar una ruta de dependencia entre los dos proporciona la respuesta.

Dos funciones pueden ayudarte a encontrar rutas de acceso de dependencia: somepath() y allpaths(). A partir de un destino S y un punto de destino E, busca una ruta entre S y E mediante somepath(S,E).

Explora las diferencias entre estas dos funciones y observa las relaciones entre los objetivos de "Chef" y "Queso". Existen diferentes rutas posibles para ir de un destino al otro:

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

somepath() te brinda 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 Café → Chef → MacAndCheese → Cheese. En cambio, si 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 salida de la cafetería al chef, la pizza, los macarrones con el queso y el queso

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

Ponte a prueba

Uno de los clientes de Cafe Bazel dio la primera opinión del restaurante. Lamentablemente, faltan algunos detalles en la opinión, como la identidad del usuario que dejó la opinión y el plato al que hace referencia. Por suerte, 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 ejecútalo con lo siguiente:

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

Si solo realizas consultas de Bazel, intenta averiguar quién escribió la opinión y qué plato describía.

Pista

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

Respuesta

Esta opinión describía la pizza y Antonio fue el revisor. Si analizas qué dependencias tenía esta regla con 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 consulta para buscar qué etiqueta tiene Antonio en el archivo `BUILD` para ver qué plato tiene. El resultado del comando bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' indica que Amir es el único cliente que pidió una pizza y es el revisor que nos da la respuesta.

Conclusión

¡Felicitaciones! Ya ejecutaste varias consultas básicas, que puedes probar en tus propios proyectos. Si deseas obtener más información sobre la sintaxis del lenguaje de consulta, visita la página de referencia de las consultas. ¿Deseas realizar consultas más avanzadas? En la Guía de consultas, se muestra una lista detallada de más casos de uso de los que se abordan en esta guía.