Guía de inicio rápido de consultas

En este instructivo, se explica cómo trabajar con Bazel para rastrear dependencias en tu código con un proyecto de Bazel precompilado.

Para obtener detalles sobre el lenguaje y la marca --output, consulta los manuales de referencia de Bazel query y Bazel cquery. 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 te guiará por un conjunto de consultas básicas que puedes usar para obtener más información sobre las dependencias de archivos de tu proyecto. Está destinada a desarrolladores nuevos de Bazel con conocimientos básicos sobre cómo funcionan Bazel y los archivos BUILD.

Requisitos previos

Comienza por instalar Bazel, si aún no lo hiciste. En este instructivo, se usa Git para el control de código fuente, por lo que, para obtener mejores resultados, también instala Git.

Para visualizar los gráficos de dependencias, se usa la herramienta llamada Graphviz, que puedes descargar para seguir los pasos.

Obtén el proyecto de muestra

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

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 examen del resultado para obtener información útil. En esta guía, se muestra una vista previa de algunas funciones de consulta básicas, pero para obtener más opciones, consulta la guía de consultas. Las consultas te ayudan a obtener información sobre las dependencias en proyectos a gran escala sin navegar manualmente 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 profundice en la relación entre Cafe Bazel y su chef respectivo. Este café vende exclusivamente pizza y macarrones con queso. Observa a continuación cómo se estructura 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

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

Un proyecto consta de diferentes paquetes que conforman un café. Se separan en: restaurant, ingredients, dishes, customers y reviews. Las reglas dentro de estos paquetes definen diferentes componentes del café con varias etiquetas y dependencias.

Cómo ejecutar una compilación

Este proyecto contiene un método principal dentro de Runner.java que puedes ejecutar para imprimir un menú del café. Compila el proyecto con Bazel con el comando bazel build y usa : para indicar que el destino se llama runner. Consulta los nombres de destino para obtener información sobre cómo hacer referencia a los 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 se realiza correctamente.

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 que se compile correctamente, ejecuta la aplicación pegando este comando:

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

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

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

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

Explora destinos

El proyecto enumera los ingredientes y los platos en sus propios paquetes. Para 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 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 destinos 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

Cómo encontrar dependencias

¿En qué destinos se basa tu ejecutor para ejecutarse?

Supongamos que quieres profundizar en la estructura de tu proyecto sin indagar en el sistema de archivos (lo que puede ser insostenible para 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 ejecutando el siguiente 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 la sección, se describe cómo puedes visualizar las rutas de acceso de dependencias para una consulta específica. Graphviz ayuda a ver la ruta de acceso como una imagen de gráfico acíclico dirigido en lugar de una lista aplanada. Puedes modificar la visualización del gráfico de consultas de Bazel con varias opciones de línea de comandos --output. Consulta Formatos de salida para obtener opciones.

Comienza por ejecutar la consulta que desees y agrega la marca --noimplicit_deps para quitar las dependencias de herramientas excesivas. Luego, sigue la consulta con la marca de salida 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 destino :runner y dar formato al 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 gráfico de compilación. Graphviz usa dot – una herramienta que procesa texto en una visualización — para crear un png:

dot -Tpng < graph.in > graph.png

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

Diagrama que muestra la relación entre la cafetería, el chef y los platos: pizza y macarrones con queso, que se dividen en los ingredientes separados: queso, tomates, masa y macarrones.

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

Cómo encontrar dependencias inversas

Si, en cambio, tienes un destino que te gustaría analizar qué otros destinos usan, puedes usar una consulta para examinar qué destinos dependen de una regla determinada. Esto se denomina “dependencia inversa”. El uso de rdeps() puede ser útil cuando editas un archivo en una base de código con la que no estás familiarizado y puede evitar que, sin saberlo, rompas otros archivos que dependían de él.

Por ejemplo, quieres hacer algunos cambios en el ingrediente cheese. Para evitar causar un problema en Cafe Bazel, debes verificar qué platos dependen de cheese.

Para ver qué destinos dependen de un destino 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 pertinente) 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 entero que especifica el límite superior de la profundidad de la búsqueda.

Para buscar dependencias inversas del destino cheese 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

La devolución de la consulta muestra que tanto la pizza como los macarrones con queso dependen del queso. ¡Qué sorpresa!

Cómo encontrar destinos según etiquetas

Dos clientes entran en Bazel Cafe: Amir y Jenny. No se sabe nada de ellos, excepto sus nombres. Por suerte, sus pedidos están etiquetados en el archivo BUILD de “clientes”. ¿Cómo puedes acceder a esta etiqueta?

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

En este ejemplo, las etiquetas son pizza o macAndCheese para representar los elementos del menú. Este comando consulta los 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 destinos del paquete “clientes” que tienen una etiqueta de “pizza”.

Comprueba tus conocimientos

Usa esta consulta para saber qué quiere pedir Jenny.

Respuesta

Macarrones con queso

Cómo agregar una dependencia nueva

Cafe Bazel amplió 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 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 de Java para cada ingrediente nuevo, incluido su nombre, visibilidad pública y su archivo “src” recién creado. 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, debes agregar una regla nueva para Smoothie. Si lo haces, se incluye el archivo Java creado para Smoothie como un 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 del 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 el “café”. Si no es así, busca errores ortográficos y nombres de paquetes. Para obtener más información sobre cómo escribir BUILD archivos, consulta la Guía de estilo de BUILD.

Ahora, visualiza el nuevo gráfico de dependencias con la adición del Smoothie para compararlo con el anterior. Para mayor claridad, asigna a la entrada del gráfico los nombres 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 que el primero, excepto que ahora hay un radio que se origina en el destino del chef con batido, que lleva a banana y fresa.

Si observas graph2.png, puedes ver que Smoothie no tiene dependencias compartidas con otros platos, sino que es solo otro destino del que depende el Chef.

somepath() y allpaths()

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

Dos funciones pueden ayudarte a encontrar rutas de acceso de dependencias: somepath() y allpaths(). Dado un destino inicial S y un punto final E, busca una ruta de acceso entre S y E con somepath(S,E).

Explora las diferencias entre estas dos funciones observando las relaciones entre los destinos “Chef” y “Cheese”. Hay diferentes rutas de acceso posibles para ir de un destino a otro:

  • Chef → MacAndCheese → Cheese
  • Chef → Pizza → Cheese

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

Si usas 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 acceso 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 de salida de café a chef,de pizza y macarrones con queso a queso

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

Comprueba tus conocimientos

Uno de los clientes de Cafe Bazel dio la primera opinión del restaurante. Lamentablemente, la opinión no incluye algunos detalles, como la identidad del revisor y a qué plato 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. Compílalo 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 usas consultas de Bazel, intenta averiguar quién escribió la opinión y qué plato describió.

Pista

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

Respuesta

En esta opinión, se describía la pizza, y Amir fue el revisor. Si observas las dependencias que 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. A continuación, como sabes que el revisor es Amir, puedes usar la función de consulta para buscar qué etiqueta tiene Amir en el archivo `BUILD` para ver qué plato hay allí. El 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, lo que nos da la respuesta.

Conclusión

¡Felicitaciones! Ahora ejecutaste varias consultas básicas que puedes probar en tus propios proyectos. Para obtener más información sobre la sintaxis del lenguaje de consultas, consulta la página de referencia de consultas. ¿Quieres consultas más avanzadas? En la guía de consultas, se muestra una lista detallada de más casos de uso que los que se abarcan en esta guía.