En este instructivo, se explica cómo trabajar con Bazel para hacer un seguimiento de las dependencias en tu código con un proyecto de Bazel prediseñado.
Para obtener detalles sobre el lenguaje y la marca --output
, consulta los manuales de referencia de Bazel query y referencia de 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 explican una serie de consultas básicas que puedes usar para obtener más información sobre las dependencias de archivos de tu proyecto. Está dirigido a desarrolladores nuevos de Bazel con conocimientos básicos sobre el funcionamiento de 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 debes instalar Git.
Para visualizar los gráficos de dependencia, se usa la herramienta Graphviz, que puedes descargar para seguir el proceso.
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
.
Cómo comenzar
¿Qué son las consultas de Bazel?
Las consultas te ayudan a conocer una base de código de Bazel analizando las relaciones entre los archivos BUILD
y examinando el resultado para obtener información útil. En esta guía, se muestran algunas funciones de consulta básicas, pero, para obtener más opciones, consulta la guía de consultas. Las búsquedas te ayudan a obtener información sobre las dependencias en proyectos a gran escala sin tener que navegar manualmente por los archivos BUILD
.
Para ejecutar una consulta, abre la terminal de línea de comandos y, luego, ingresa lo siguiente:
bazel query 'query_function'
Situación
Imagina una situación que profundice en la relación entre Café Bazel y su chef. En este café, solo se venden pizzas y macarrones con queso. A continuación, puedes ver 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
└── WORKSPACE
A lo largo de este instructivo, a menos que se indique lo contrario, intenta no buscar en los archivos BUILD
la información que necesitas y, en su lugar, usa solo la función de consulta.
Un proyecto consta de diferentes paquetes que componen una cafetería. Se separan en: restaurant
, ingredients
, dishes
, customers
y reviews
. Las reglas dentro de estos paquetes definen diferentes componentes de la cafetería 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ú de la cafetería. Compila el proyecto con Bazel usando 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
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
Después de que se compile correctamente, pega este comando para ejecutar la aplicación:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Esto te dejará con una lista de los elementos del menú junto con una breve descripción.
Explora los objetivos
El proyecto enumera los ingredientes y los platos en sus propios paquetes. Para usar una consulta y 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 este café ejecutando el siguiente comando:
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é objetivos se basa tu corredor para correr?
Supongamos que deseas profundizar en la estructura de tu proyecto sin explorar 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 esta sección, se describe cómo puedes visualizar las rutas de dependencia de una búsqueda específica. Graphviz ayuda a ver la ruta como una imagen de grafo 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 la línea de comandos --output
. Consulta Formatos de salida para ver las opciones.
Para comenzar, ejecuta la consulta que desees y agrega la marca --noimplicit_deps
para quitar las dependencias excesivas de herramientas. Luego, agrega la marca de salida a la consulta 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 lo siguiente. El siguiente gráfico se simplificó para que los detalles esenciales de la ruta sean más claros en esta guía.
Esto resulta útil cuando deseas ver los resultados de las diferentes funciones de consulta a lo largo de esta guía.
Cómo encontrar dependencias inversas
Si, en cambio, tienes un destino y quieres analizar qué otros destinos lo usan, puedes usar una consulta para examinar qué destinos dependen de una regla determinada. Esto se denomina “dependencia inversa”. Usar 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, dañes otros archivos que dependían de él.
Por ejemplo, quieres hacer algunos cambios en el ingrediente cheese
. Para evitar problemas 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 las 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
El resultado de la consulta muestra que tanto pizza como macAndCheese dependen de cheese. ¡Qué sorpresa!
Cómo encontrar objetivos según las etiquetas
Dos clientes entran a Bazel Cafe: Amir y Jenny. No se sabe nada de ellos, excepto sus nombres. Por suerte, tienen sus pedidos 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 no tienen ninguna capacidad de anotación en el tiempo 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 confirmación de un código base.
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 devuelve todos los destinos del paquete "clientes" que tienen la etiqueta "pizza".
Comprueba tus conocimientos
Usa esta búsqueda para saber qué quiere pedir Jenny.
Respuesta
Macarrones con queso
Cómo agregar una dependencia nueva
Café Bazel amplió su menú: ahora los clientes pueden pedir un smoothie. Este batido específico consta de los ingredientes Strawberry
y Banana
.
Primero, agrega los ingredientes de los que depende el smoothie: 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 el archivo "src" recién creado. Deberías obtener 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
para platos, deseas agregar una regla nueva para Smoothie
. Si lo haces, se incluirá el archivo Java creado para Smoothie
como un archivo "src" y las nuevas reglas que creaste para cada ingrediente del 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 ú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 compila correctamente, ¡felicidades! Agregaste una nueva dependencia para "Cafe". De lo contrario, busca errores ortográficos y de nombres de paquetes. Para obtener más información sobre cómo escribir archivos BUILD
, consulta la Guía de estilo de BUILD.
Ahora, visualiza el nuevo gráfico de dependencias con la incorporación de Smoothie
para compararlo con el anterior. Para mayor claridad, asigna a las entradas 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
Si observas graph2.png
, puedes ver que Smoothie
no tiene dependencias compartidas con otros platos, sino que es otro destino del que depende Chef
.
somepath() y allpaths()
¿Qué sucede si quieres 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 acceso de dependencias: somepath()
y allpaths()
. Dado un objetivo inicial S y un punto final E, encuentra una ruta entre S y E con somepath(S,E)
.
Explora las diferencias entre estas dos funciones observando las relaciones entre los objetivos "Chef" y "Queso". Existen diferentes rutas posibles para ir de un objetivo a otro:
- Chef → MacAndCheese → Cheese
- Chef → Pizza → Queso
somepath()
te proporciona una sola ruta de las dos opciones, mientras que "allpaths()" genera todas las rutas 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 Cafe → 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
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 facilita la comprensión de la relación.
Comprueba tus conocimientos
Uno de los clientes de Café Bazel dejó la primera opinión sobre el restaurante. Lamentablemente, a la opinión le faltan algunos detalles, como la identidad del usuario que la escribió y a qué plato 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 el siguiente comando:
bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review
Basándote solo en las consultas de Bazel, intenta averiguar quién escribió la opinión y qué plato describía.
Pista
Verifica las etiquetas y las dependencias para obtener información útil.
Respuesta
En esta opinión, se describía la pizzería, y Amir era 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.
Luego, como sabes que el revisor es Amir, puedes usar la función de consulta para buscar qué etiqueta tiene Amir en el archivo `BUILD` y ver qué plato hay allí.
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, 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 realizar búsquedas 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.