DEV Community

Jorge Cano
Jorge Cano

Posted on • Originally published at Medium on

Entendiendo Bazel

Para arrancar bazel es una herramienta que utiliza google para compilar TODOS sus códigos fuentes, basado en un mono-repo (lo que quiere decir es que todo el código de google se encuentra en un solo repositorio) Podes imaginarte cuanta cantidad de lineas de código, lenguajes y configuraciones distintas en un mismo repositorio.

Que locura no? Y nosotros a veces nos quejamos de tener alguna diferencia entre dev y producción xD …

vamos a hacer un pequeño parentesis en todo esto, imagino que se estaran preguntando porque en todo esto tenemos que entender:

porque y para que sirve bazel

La realidad es que angular esta migrando sus funcionalidades hacia bazel, para que podamos mejorar en nuestros tiempos de desarrollo, transpilación y compilación. Tambien es importante entender que los equipos de google (que tienen alrededor de 600 apps realizadas con angular), no pueden usar el CLI porque usan bazel… pero hace poquitos días ya se pudo ver los primeros avances interesantes en este merge entre bazel y webpack… entonces para poder explicar como funciona lo demas, tienen que entender como funciona bazel.

El primer punto que tenemos que entender es que bazel se basa en una serie de conceptos y terminología que tenemos que ir aprendiendo.

Bazel crea software a partir del código fuente organizado en un directorio llamado WORKSPACE. Los archivos de origen en el workspace están organizados en una jerarquía anidada de paquetes, donde cada paquete es un directorio que contiene un conjunto de archivos fuente relacionados y un archivo BUILD. El archivo BUILD especifica qué salidas de software se pueden generar a partir de la fuente.

Workspace

Un Workspace es un directorio en su sistema de archivos que contiene los archivos de origen para el software que vamos a programar, así como enlaces simbólicos a directorios que contienen los resultados de compilación. Cada directorio del espacio de trabajo tiene un archivo de texto llamado WORKSPACE que puede estar vacío, o puede contener referencias a dependencias externas necesarias para construir los resultados.

Packages

La unidad primaria de organización del código en un workspace es el paquete. Un paquete es una colección de archivos relacionados y una especificación de las dependencias entre ellos.

Un paquete se define como un directorio que contiene un archivo llamado BUILD, que reside debajo del directorio de nivel superior en el espacio de trabajo. Un paquete incluye todos los archivos en su directorio, más todos los subdirectorios debajo de él, excepto aquellos que contienen un archivo BUILD.

Por ejemplo, en el siguiente árbol de directorios hay dos paquetes, my/app y el sub-paquete my/app/tests. Tenga en cuenta que my/app/data no es un paquete, sino un directorio que pertenece al paquete my/app.

src/my/app/BUILD  
src/my/app/app.ts  
src/my/app/data/input.txt  
src/my/app/tests/BUILD  
src/my/app/tests/test.ts  
Enter fullscreen mode Exit fullscreen mode

targets

Un package es un contenedor. Los elementos de un package se llaman targets. La mayoría de los targets son uno de los dos principales tipos, archivos y reglas. Además, hay otro tipo de targets, grupos de package, pero son mucho menos numerosos.

Los archivos se dividen en dos tipos. Los archivos de origen generalmente se escriben por el esfuerzo de las personas y se registran en el repositorio. Los archivos generados, a veces llamados archivos derivados, no se incorporan, pero la herramienta de compilación los genera a partir de los archivos fuente de acuerdo con reglas específicas.

El segundo tipo de target es la regla(rule). Una regla especifica la relación entre un conjunto de entrada y un conjunto de archivos de salida, incluidos los pasos necesarios para derivar los resultados de las entradas. Los resultados de una regla son siempre archivos generados. Las entradas a una regla pueden ser archivos fuente, pero también pueden ser archivos generados; en consecuencia, los resultados de una regla pueden ser las entradas a otra, lo que permite construir largas cadenas de reglas.

Si la entrada a una regla es un archivo fuente o un archivo generado es en la mayoría de los casos no es importante; lo que importa es solo el contenido de ese archivo. Este hecho hace que sea fácil reemplazar un archivo fuente complejo con un archivo generado por una regla, como ocurre cuando la carga de mantener manualmente un archivo altamente estructurado se vuelve demasiado molesto, y alguien escribe un programa para derivarlo. No se requiere ningún cambio para los consumidores de ese archivo. Por el contrario, un archivo generado puede ser reemplazado fácilmente por un archivo fuente con solo cambios locales.

Las entradas a una regla también pueden incluir otras reglas. El significado preciso de tales relaciones suele ser bastante complejo y dependiente del lenguaje o de la regla, pero intuitivamente es simple: una regla A de la biblioteca C ++ podría tener otra regla B de la biblioteca C ++ para una entrada. El efecto de esta dependencia es que los archivos de encabezado B están disponibles para A durante la compilación, los símbolos B están disponibles para A durante el enlace, y los datos de tiempo de ejecución de B están disponibles para A durante la ejecución.

Una variante de todas las reglas es que los archivos generados por una regla siempre pertenecen al mismo paquete que la regla en sí; no es posible generar archivos en otro paquete. Sin embargo, no es raro que las entradas de una regla provengan de otro paquete.

Los grupos de paquetes son conjuntos de paquetes cuyo propósito es limitar el acceso a ciertas reglas. Los grupos de paquetes se definen mediante la función package_group. Tienen dos propiedades: la lista de paquetes que contienen y su nombre. Las únicas formas permitidas de referirse a ellas son desde el atributo de visibilidad de las reglas o desde el atributo default_visibility de la función de paquete; no generan ni consumen archivos

Labels

Todos los targets pertenecen exactamente a un package. El nombre de un target se llama su label, y una label típica en forma canónica se ve así:

//my/app/main:app_binary

Cada label tiene dos partes, un *nombre de paquete* (*my/app/main*) y un nombre de destino (*app_binary*). Cada label identifica de manera única un objetivo. Las etiquetas a veces aparecen en otras formas; cuando se omiten los dos puntos, se supone que el nombre del objetivo es el mismo que el último componente del nombre del paquete, por lo que estas dos etiquetas son equivalentes:

//my/app  
//my/app:app

Enter fullscreen mode Exit fullscreen mode

Los labels de formato corto como /my/app no deben confundirse con los nombres de los packages. Los labels comienzan con //, pero los nombres de los packages nunca lo hacen, por lo tanto my/app es el package que contiene //my/app. (Una idea errónea común es que //my/app se refiere a un package, o a todos los objetivos en un paquete; ninguno es verdadero).

Dentro de un archivo BUILD, la parte del nombre del package del label puede omitirse y, opcionalmente, también los dos puntos. Entonces, dentro del archivo BUILD para el package my/app (es decir, //my/app:BUILD), los siguientes labels “relativos” son todos equivalentes:

//my/app:app  
//my/app  
:app  
app  
Enter fullscreen mode Exit fullscreen mode

(Es una cuestión de convención que los dos puntos se omiten para los archivos, pero se retienen para las reglas, pero no es significativo).

Del mismo modo, dentro de un archivo BUILD, los archivos que pertenecen al package pueden ser referenciados por su nombre sin adornos relativo al directorio del paquete:

generate.cc  
testdata/input.txt  
Enter fullscreen mode Exit fullscreen mode

Rules

Una regla especifica la relación entre las entradas y las salidas, y los pasos para construir las salidas. Las reglas pueden ser de uno de muchos tipos o clases diferentes, que producen ejecutables compilados y bibliotecas, ejecutables de prueba y otros resultados compatibles, tal como se describe en Build Encyclopedia.

Cada regla tiene un nombre, especificado por el atributo de nombre, de tipo cadena. El nombre debe ser un nombre de objetivo sintácticamente válido, como se especifica arriba. En algunos casos, el nombre es algo arbitrario, y más interesantes son los nombres de los archivos generados por la regla; esto es cierto de genrules. En otros casos, el nombre es significativo: para las reglas * _binary y * _test, por ejemplo, el nombre de la regla determina el nombre del ejecutable producido por la compilación.

Cada regla tiene un conjunto de atributos; los atributos aplicables para una regla dada, y el significado y la semántica de cada atributo son una función de la clase de la regla; consulte Build Encyclopedia para obtener la lista completa de las reglas admitidas y sus atributos correspondientes. Cada atributo tiene un nombre y un tipo. Algunos de los tipos comunes que un atributo puede tener sone integer, label, list of labels, string, list of strings, output label, list of output labels.. No todos los atributos deben especificarse en cada regla. Los atributos forman así un diccionario de claves (nombres) a valores tipados opcionales.

El atributo *srcs* presente en muchas reglas tiene el tipo “list of label”; su valor, si está presente, es una lista de etiquetas, cada una es el nombre de un objetivo que es una entrada a esta regla.

El atributo de salida presente en muchas reglas tiene el tipo “list of outputs labels“; esto es similar al tipo del atributo srcs, pero difiere de dos maneras significativas. En primer lugar, debido a la invariabilidad de que los resultados de una regla pertenecen al mismo paquete que la regla en sí, las etiquetas de salida no pueden incluir un componente de paquete; deben estar en una de las formas “relativas” que se muestran arriba. En segundo lugar, la relación implicada por un atributo de etiqueta (ordinario) es inversa a la implícita en una etiqueta de salida: una regla depende de sus srcs, mientras que una regla depende de sus salidas. Los dos tipos de atributos de etiqueta asignan dirección a los bordes entre los objetivos, dando lugar a un gráfico de dependencia.

Este gráfico acíclico dirigido sobre los objetivos se denomina “gráfico objetivo” o “gráfico de dependencia de compilación”, y es el dominio sobre el que opera la herramienta Bazel Query.

Build file

La sección anterior describía los packages, los targets y las labels, y el gráfico de dependencia de compilación en forma abstracta. En esta sección, veremos la sintaxis concreta utilizada para definir un package.

Por definición, cada paquete contiene un archivo BUILD, que es un programa corto. La mayoría de los archivos BUILD parecen ser poco más que una serie de declaraciones de reglas de compilación; de hecho, el estilo declarativo se recomienda encarecidamente al escribir archivos BUILD.

Sin embargo, los archivos BUILD se evalúan usando un lenguaje imperativo, Skylark.

Declaring build rules

Los archivos BUILD utilizan un lenguaje imperativo, por lo que, en general, el orden sí importa: las variables se deben definir antes de usarlas, por ejemplo. Sin embargo, la mayoría de los archivos BUILD constan solo de declaraciones de reglas de compilación, y el orden relativo de estas afirmaciones es irrelevante; lo único que importa es qué reglas se declararon, y con qué valores, cuando la evaluación del paquete finaliza. Por lo tanto, en los archivos BUILD simples, las declaraciones de reglas se pueden reordenar libremente sin cambiar el comportamiento.

Se alienta a los autores de archivos BUILD a usar los comentarios generosamente para documentar el rol de cada objetivo de compilación, ya sea para uso público y cualquier otra cosa que ayude a los usuarios y a los futuros mantenedores, incluyendo un comentario en la parte superior, explicando el papel del paquete .

La sintaxis de comentario de Python de # … es compatible. Los literales de cadena de comillas triples pueden abarcar varias líneas y se pueden usar para comentarios de varias líneas.

Types of build rule

La mayoría de las reglas de compilación vienen en familias, agrupadas por idioma. Por ejemplo, cc_binary, cc_library y cc_test son las reglas de compilación para los binarios, las bibliotecas y las pruebas de C ++, respectivamente. Otros idiomas usan el mismo esquema de nombres, con un prefijo diferente, p. java_ * para Java. Estas funciones están documentadas en Build Encyclopedia.

* *_binary Las reglas de binary crean programas ejecutables en un idioma determinado. Después de una compilación, el ejecutable residirá en el árbol de salida binaria de la herramienta de compilación con el nombre correspondiente para la etiqueta de la regla, por lo que //my:program aparecerá en (por ejemplo) $(BINDIR)/my/program.

Dichas reglas también crean un directorio de archivos de ejecución que contiene todos los archivos mencionados en un atributo de datos que pertenece a la regla, o cualquier regla en su cierre transitivo de dependencias; este conjunto de archivos se reúne en un solo lugar para facilitar el despliegue a la producción.

* *_test Las reglas de prueba son una especialización de una regla * _binary, usada para pruebas automatizadas. Las pruebas son simplemente programas que devuelven cero en el éxito.

Al igual que los archivos binarios, las pruebas también tienen árboles de ejecución y los archivos que se encuentran debajo son los únicos archivos que una prueba puede abrir legítimamente en el tiempo de ejecución. Por ejemplo, un programa cc_test (name = ‘x’, data = [‘// foo: bar’]) puede abrir y leer $TEST_SRCDIR/workspace/foo/bar durante la ejecución. (Cada lenguaje de programación tiene su propia función de utilidad para acceder al valor de $ TEST_SRCDIR, pero todos son equivalentes al uso directo de la variable de entorno.) Si no se observa la regla, la prueba fallará cuando se ejecute en un host de prueba remoto .

* *_library rules especifica los módulos compilados por separado en el lenguaje de programación proporcionado. Las bibliotecas pueden depender de otras bibliotecas, y los binarios y las pruebas pueden depender de las bibliotecas, con el comportamiento esperado de compilación independiente.

Dependencies

Un target A depende de un target B si B es necesario por A en el tiempo de construcción o ejecución. El depende de la relación induce un gráfico acíclico dirigido (DAG) sobre los objetivos, y lo llamamos un gráfico de dependencia. Las dependencias directas de un objetivo son aquellos otros objetivos alcanzables por una ruta de longitud 1 en el gráfico de dependencia. Las dependencias transitivas de un objetivo son aquellos objetivos de los que depende a través de una ruta de cualquier longitud a través del gráfico.

De hecho, en el contexto de construcciones, hay dos gráficos de dependencia, el gráfico de dependencias reales y el gráfico de dependencias declaradas. La mayoría de las veces, los dos gráficos son tan similares que no es necesario hacer esta distinción, pero es útil para la discusión a continuación.

Types of dependencies

La mayoría de las reglas de compilación tienen tres atributos para especificar diferentes tipos de dependencias genéricas: srcs, deps y data. Estos se explican a continuación.

Muchas reglas también tienen atributos adicionales para clases de dependencia específicas de reglas compiler, resources, etc. E

srcs

Archivos consumidos directamente por la regla o reglas que generan archivos fuente.

deps

Regla que apunta a módulos compilados por separado que proporcionan archivos de encabezado, símbolos, bibliotecas, datos, etc.

data

Un target de compilación puede necesitar algunos archivos de datos para ejecutarse correctamente. Estos archivos de datos no son código fuente: no afectan cómo se construye el target. Por ejemplo, una prueba unitaria puede comparar la salida de una función con el contenido de un archivo. Cuando construimos la prueba unitaria, no necesitamos el archivo; pero lo necesitamos cuando hacemos la prueba. Lo mismo se aplica a las herramientas que se lanzan durante la ejecución.

El sistema de compilación ejecuta pruebas en un directorio aislado donde solo están disponibles los archivos que figuran como “datos”. Por lo tanto, si un archivo binario / biblioteca / prueba necesita algunos archivos para ejecutarse, especifíquelos (o una regla de compilación que los contenga) en los datos.

Aca dejo un ejemplo de como funciona en angular sin ningun tipo de “bridge” entre el CLI y Bazel….

jorgeucano/ngx-bazel-ngrx-template

En el proximo post, voy a estar explicando como funciona Bazel con Angular y el angular CLI , así podemos empezar a sacarle provecho a esta magnifica combinación.

Como siempre, espero que les guste lo compartido y cualquier duda dejo mis canales:

Saludos

Top comments (0)