En este instructivo, se explica cómo trabajar con Bazel para hacer un seguimiento de las dependencias en tu código mediante un proyecto de Bazel prefabricado.
Para obtener detalles sobre el idioma y la marca --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 muestra 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 nuevos desarrolladores de Bazel con conocimientos básicos sobre el funcionamiento de los archivos de Bazel y BUILD
.
Requisitos previos
Primero, instala Bazel si aún no lo hiciste. En este instructivo, se usa Git para controlar el código fuente, así que, si deseas obtener los mejores resultados, también instala Git.
Para visualizar gráficos de dependencias, se usa la herramienta Graphviz, que puedes descargar si deseas continuar.
Obtén el proyecto de muestra
A continuación, recupera la app de ejemplo del repositorio de ejemplos de Bazel ejecutando lo siguiente en la herramienta de línea de comandos que elijas:
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 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 en busca de información útil. En esta guía se muestra una vista previa de algunas funciones de consulta básicas, pero para ver más opciones, consulta la guía de consultas. Las consultas te ayudan a aprender sobre dependencias en proyectos a gran escala sin tener que 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 profundice en la relación entre Cafe Bazel y su respectivo chef. Este café vende exclusivamente pizzas y macarrones y de queso. Observa a continuación 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
A lo largo de este instructivo, a menos que se te indique lo contrario, intenta no buscar en los archivos BUILD
para encontrar la información que necesitas y, en su lugar, usa 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 dentro de estos paquetes definen diferentes componentes de la cafetería 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 la Cafetería. Compila el proyecto usando Bazel con el comando
bazel build
y usa :
para indicar que el destino se llama runner
. Consulta
target-name para aprender a
destinos de referencia.
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 este comando para ejecutar la aplicación:
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
Verás una lista de los elementos del menú 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 usar esto para buscar los ingredientes y 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 verse de la siguiente manera:
//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
Encontrar dependencias
¿Qué objetivos utiliza tu corredor para correr?
Digamos que quieres profundizar en la estructura de tu proyecto sin incrustar en el sistema de archivos (que puede ser insostenible para proyectos grandes). ¿Qué reglas usa Cafe Bazel?
Si, como en este ejemplo, el destino del ejecutor es runner
, ejecuta el 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 destino específico.
Visualiza el gráfico de dependencias (opcional)
En la sección, se describe cómo visualizar las rutas de dependencias para una consulta específica. Graphviz ayuda a ver la ruta como una imagen de grafo acíclica dirigida en lugar de una lista compactada. Puedes modificar la visualización del gráfico de consulta de Bazel usando varias opciones de línea de comandos --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 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 darle 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 y lo convierte en una visualización, para crear un png:
dot -Tpng < graph.in > graph.png
Si abres graph.png
, deberías ver algo similar a lo siguiente: El siguiente gráfico se simplificó para aclarar los detalles esenciales de la ruta en esta guía.
Esto es útil cuando quieres ver los resultados de las diferentes funciones de consulta que se incluyen en esta guía.
Encuentra dependencias inversas
Si, en cambio, tiene un objetivo que desea analizar qué otros objetivos lo usan, puede usar una consulta para examinar qué orientaciones 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 que no conoces y puede evitar romper inadvertidas otros archivos que dependían de él.
Por ejemplo, quieres editar el ingrediente cheese
. Para evitar causar 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()
adopta 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 pizza y macAndCheese dependen del queso. ¡Qué sorpresa!
Búsqueda de objetivos basados en etiquetas
Dos clientes entran a Bazel Cafe: Amir y Jenny. No se conoce nada sobre ellos, excepto por sus nombres. Por suerte, sus pedidos están etiquetados en la sección BUILD
. ¿Cómo puedes acceder a esta etiqueta?
Los desarrolladores pueden etiquetar los 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, especialmente para pruebas de C++ y Python, que carecen de capacidad de anotación de entorno de ejecución. El uso de etiquetas y elementos de tamaño brinda flexibilidad para organizar conjuntos de pruebas en función de la política de registro de una base de código.
En este ejemplo, las etiquetas pueden ser pizza
o macAndCheese
para representar los elementos del 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 devuelve todos los objetivos en la columna "customers" que tenga 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 amplió su menú: ahora los clientes pueden pedir batidos 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 biblioteca de Java nueva para cada ingrediente nuevo, incluido su nombre, visibilidad pública y su “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
para platos, quieres agregar una nueva regla para Smoothie
. Esto incluye el archivo Java creado para Smoothie
como "src" y las nuevas reglas que estableciste
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, querrás 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 la versión es exitosa, ¡felicitaciones! Agregaste una nueva dependencia para "Cafe". De lo contrario, presta atención a los errores ortográficos y la denominación 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 la dependencia junto con Smoothie
para compararlo con el anterior. Para mayor claridad, nombra la entrada del gráfico como 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, pero es solo otro objetivo del que se basa Chef
.
somepath() y allpaths()
¿Qué sucede si quieres consultar por qué un paquete depende de otro? La respuesta es mostrar una ruta de dependencia entre los dos.
Dos funciones pueden ayudarte a encontrar rutas de dependencia: somepath()
y allpaths()
. Con un objetivo de inicio S y un extremo E, encuentra una ruta entre S y E mediante somepath(S,E)
.
Explora las diferencias entre estas dos funciones al observar las relaciones entre el 'Chef'. y "Queso" objetivos. Existen diferentes rutas posibles para ir de un destino a otro:
- Chef → MacAndCheese → Queso
- Chef → Pizza → Queso
somepath()
te da una sola ruta de acceso entre 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
La salida sigue el primer camino de Cafe → Chef → MacAndCheese → Cheese. Si, en cambio, usas allpaths()
, obtienes 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 plana de las dependencias. Si visualizas este gráfico con Graphviz, es más fácil entender la relación.
Ponte a prueba
Uno de los clientes de Cafe Bazel dio la primera opinión sobre el restaurante. Lamentablemente, faltan algunos detalles en la opinión, como la identidad del usuario que dejó la opinión y a qué plato se refiere. Por suerte, puedes acceder a esta información con Bazel. El paquete reviews
contiene un programa que imprime la 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
Solo consultando las consultas sobre Bazel, intenta averiguar quién escribió la opinión y qué plato describía.
Pista
Verifica las etiquetas y dependencias para obtener información útil.
Respuesta
En esta opinión describía a Pizza y Amir era el revisor. Si observas 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.
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` 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 que nos da la respuesta.
Conclusión
¡Felicitaciones! Ya ejecutaste varias consultas básicas, que puedes probar en tus propios proyectos. Para obtener más información sobre la sintaxis del lenguaje de consulta, visita la página de referencia de consultas. ¿Quieres realizar 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 abordan en esta guía.