Bazel admite dependencias externas, archivos de origen (tanto de texto como binarios) que se usan en tu compilación y que no son de tu espacio de trabajo. Por ejemplo, pueden ser un conjunto de reglas alojado en un repositorio de GitHub, un artefacto de Maven o un directorio en tu máquina local fuera de tu espacio de trabajo actual.
En este documento, se proporciona una descripción general del sistema antes de examinar algunos de los conceptos con más detalle.
Descripción general del sistema
El sistema de dependencias externo de Bazel funciona en función de los módulos de Bazel, cada uno de los cuales es un proyecto de Bazel con versión, y los repositorios (o repos), que son árboles de directorios que contienen archivos fuente.
Bazel comienza desde el módulo raíz, es decir, el proyecto en el que estás trabajando.
Al igual que todos los módulos, debe tener un archivo MODULE.bazel
en la raíz del directorio, que declare sus metadatos básicos y sus dependencias directas. El siguiente es un ejemplo básico:
module(name = "my-module", version = "1.0")
bazel_dep(name = "rules_cc", version = "0.1.1")
bazel_dep(name = "platforms", version = "0.0.11")
Desde allí, Bazel busca todos los módulos de dependencia transitiva en un
registro de Bazel (de forma predeterminada, el registro central de Bazel). El registro proporciona los archivos MODULE.bazel
de las dependencias, lo que permite que Bazel descubra todo el gráfico de dependencias transitivas antes de realizar la resolución de versiones.
Después de la resolución de versión, en la que se selecciona una versión para cada módulo, Bazel vuelve a consultar el registro para aprender a definir un repositorio para cada módulo, es decir, cómo se deben recuperar las fuentes de cada módulo de dependencia. La mayoría de las veces, solo son archivos descargados de Internet y extraídos.
Los módulos también pueden especificar datos personalizados llamados etiquetas, que consumen las extensiones de módulos después de la resolución de módulos para definir repositorios adicionales. Estas extensiones pueden realizar acciones como la E/S de archivos y el envío de solicitudes de red. Entre otras cosas, permiten que Bazel interactúe con otros sistemas de administración de paquetes y, al mismo tiempo, respeta el gráfico de dependencias compilado a partir de módulos de Bazel.
Los tres tipos de repositorios (el repositorio principal, que es el árbol de origen en el que trabajas, los repositorios que representan módulos de dependencia transitiva y los repositorios creados por extensiones de módulos) forman el espacio de trabajo.
Los repositorios externos (no principales) se recuperan a pedido, por ejemplo, cuando se hace referencia a ellos con etiquetas (como @repo//pkg:target
) en archivos BUILD.
Beneficios
El sistema de dependencias externas de Bazel ofrece una amplia variedad de beneficios.
Resolución de dependencia automática
- Deterministic Version Resolution: Bazel adopta el algoritmo de resolución de versiones determinista MVS, que minimiza los conflictos y aborda los problemas de dependencia de diamante.
- Administración de dependencias simplificada:
MODULE.bazel
declara solo dependencias directas, mientras que las dependencias transitivas se resuelven automáticamente, lo que proporciona una descripción general más clara de las dependencias del proyecto. - Visibilidad de dependencia estricta: Solo se ven las dependencias directas, lo que garantiza la exactitud y la previsibilidad.
Integración en el ecosistema
- Registro central de Bazel: Es un repositorio centralizado para descubrir y administrar dependencias comunes como módulos de Bazel.
- Adopción de proyectos que no son de Bazel: Cuando un proyecto que no es de Bazel (por lo general, una biblioteca de C++) se adapta para Bazel y se pone a disposición en BCR, se optimiza su integración para toda la comunidad y se eliminan los esfuerzos duplicados y los conflictos de los archivos de compilación personalizados.
- Integración unificada con administradores de paquetes específicos del lenguaje: Los conjuntos de reglas optimizan la integración con administradores de paquetes externos para dependencias que no son de Bazel, incluidos los siguientes:
- rules_jvm_external para Maven
- rules_python para PyPi
- bazel-gazelle para módulos de Go
- rules_rust para Cargo.
Funciones avanzadas
- Extensiones de módulo: Las funciones de extensión de módulo y
use_repo_rule
permiten el uso flexible de reglas de repositorio personalizadas y lógica de resolución para introducir dependencias que no sean de Bazel. - Comando
bazel mod
: El subcomando ofrece formas potentes de inspeccionar dependencias externas. Sabes exactamente cómo se define una dependencia externa y de dónde proviene. - Modo de proveedor: Recupera previamente las dependencias externas exactas que necesitas para facilitar las compilaciones sin conexión.
- Lockfile: El archivo de bloqueo mejora la reproducibilidad de la compilación y acelera la resolución de dependencias.
- (Próximamente) Certificación de la procedencia de BCR: Fortalece la seguridad de la cadena de suministro garantizando la procedencia verificada de las dependencias.
Conceptos
En esta sección, se proporcionan más detalles sobre los conceptos relacionados con las dependencias externas.
Módulo
Un proyecto de Bazel que puede tener varias versiones, cada una de las cuales puede tener dependencias en otros módulos.
En un lugar de trabajo de Bazel local, un módulo está representado por un repositorio.
Para obtener más detalles, consulta Módulos de Bazel.
Repositorio
Un árbol de directorios con un archivo de marcador de límite en su raíz, que contiene archivos fuente que se pueden usar en una compilación de Bazel. A menudo, se abrevia como repo.
Un archivo de marcador de límite de repo puede ser MODULE.bazel
(indica que este repo representa un módulo de Bazel), REPO.bazel
(consulta más abajo) o, en contextos heredados, WORKSPACE
o WORKSPACE.bazel
. Cualquier archivo de marcador de límite del repositorio indicará el límite de un repositorio. Varios de estos archivos pueden coexistir en un directorio.
Repositorio principal
Es el repositorio en el que se ejecuta el comando Bazel actual.
La raíz del repositorio principal también se conoce como raíz del espacio de trabajo.
Workspace
El entorno que comparten todos los comandos de Bazel se ejecuta en el mismo repositorio principal. Abarca el repositorio principal y el conjunto de todos los repositorios externos definidos.
Ten en cuenta que, históricamente, los conceptos de “repositorio” y “lugar de trabajo” se confundieron. El término “lugar de trabajo” se usó con frecuencia para referirse al repositorio principal y, a veces, incluso como sinónimo de “repositorio”.
Nombre canónico del repositorio
Es el nombre canónico al que se puede dirigir un repositorio. Dentro del contexto de un espacio de trabajo, cada repositorio tiene un solo nombre canónico. La etiqueta @@canonical_name//package:target
puede abordar un objetivo dentro de un repo cuyo nombre canónico es canonical_name
(observa el @
doble).
El repositorio principal siempre tiene la cadena vacía como nombre canónico.
Nombre aparente del repositorio
Es el nombre al que se puede dirigir un repositorio en el contexto de otro repositorio determinado.
Se puede considerar como el “sobrenombre” de un repositorio: el repositorio con el nombre canónico michael
podría tener el nombre aparente mike
en el contexto del repositorio alice
, pero podría tener el nombre aparente mickey
en el contexto del repositorio bob
. En este caso, la etiqueta @mike//package:target
puede abordar un objetivo dentro de michael
en el contexto de alice
(observa el único @
).
Por el contrario, esto se puede entender como una asignación de repositorio: cada repositorio mantiene una asignación del "nombre aparente del repositorio" a un "nombre canónico del repositorio".
Regla del repositorio
Un esquema para las definiciones de repositorio que le indica a Bazel cómo materializar un
repositorio. Por ejemplo, podría ser "descargar un archivo ZIP de una URL determinada y extraerlo", "recuperar un artefacto de Maven determinado y ponerlo a disposición como objetivo de java_import
" o simplemente "crear un symlink a un directorio local". Para definir cada repo, se llama a una regla de repo con una cantidad adecuada de argumentos.
Consulta Reglas de repositorio para obtener más información sobre cómo escribir tus propias reglas de repositorio.
Las reglas de repositorio más comunes son, con mucho, http_archive
, que descarga un archivo de una URL y lo extrae, y local_repository
, que crea un symlink a un directorio local que ya es un repositorio de Bazel.
Cómo recuperar un repositorio
Es la acción de hacer que un repositorio esté disponible en el disco local mediante la ejecución de su regla de repositorio asociada. Los repositorios definidos en un espacio de trabajo no están disponibles en el disco local antes de que se recuperen.
Por lo general, Bazel solo recupera un repositorio cuando necesita algo del repositorio y este aún no se recuperó. Si el repositorio ya se recuperó antes, Bazel solo lo vuelve a recuperar si cambió su definición.
El comando fetch
se puede usar para iniciar una actualización previa de un repositorio, un objetivo o todos los repositorios necesarios para realizar cualquier compilación. Esta función habilita compilaciones sin conexión con la opción --nofetch
.
La opción --fetch
sirve para administrar el acceso a la red. Su valor predeterminado es verdadero.
Sin embargo, cuando se establece en falso (--nofetch
), el comando usará cualquier versión almacenada en caché de la dependencia y, si no existe ninguna, el comando fallará.
Consulta opciones de recuperación para obtener más información sobre cómo controlar la recuperación.
Diseño del directorio
Después de recuperarlo, el repositorio se puede encontrar en el subdirectorio external
en la base de salida, con su nombre canónico.
Puedes ejecutar el siguiente comando para ver el contenido del repositorio con el nombre canónico canonical_name
:
ls $(bazel info output_base)/external/ canonical_name
Archivo REPO.bazel
El archivo REPO.bazel
se usa para marcar el límite superior del árbol de directorios que constituye un repositorio. No es necesario que contenga nada para que funcione como un archivo de límite del repositorio. Sin embargo, también se puede usar para especificar algunos atributos comunes para todos los destinos de compilación dentro del repositorio.
La sintaxis de un archivo REPO.bazel
es similar a la de los archivos BUILD
, excepto que no se admiten instrucciones load
. La función repo()
toma los mismos argumentos que la función package()
en los archivos BUILD
. Mientras que package()
especifica atributos comunes para todos los destinos de compilación dentro del paquete, repo()
lo hace de manera análoga para todos los destinos de compilación dentro del repositorio.
Por ejemplo, puedes especificar una licencia común para todos los destinos de tu repo si tienes el siguiente archivo REPO.bazel
:
repo(
default_package_metadata = ["//:my_license"],
)
El sistema heredado de WORKSPACE
En versiones anteriores de Bazel (antes de la 9.0), se introdujeron dependencias externas definiendo repositorios en el archivo WORKSPACE
(o WORKSPACE.bazel
). Este archivo tiene una
sintaxis similar a la de los archivos BUILD
y emplea reglas de repo en lugar de reglas de compilación.
El siguiente fragmento es un ejemplo para usar la regla de repo http_archive
en el archivo WORKSPACE
:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "foo",
urls = ["https://example.com/foo.zip"],
sha256 = "c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96",
)
El fragmento define un repositorio cuyo nombre canónico es foo
. En el sistema WORKSPACE
, de forma predeterminada, el nombre canónico de un repositorio también es su nombre aparente para todos los demás repositorios.
Consulta la lista completa de funciones disponibles en los archivos WORKSPACE
.
Desventajas del sistema WORKSPACE
En los años posteriores al lanzamiento del sistema WORKSPACE
, los usuarios informaron muchos puntos problemáticos, incluidos los siguientes:
- Bazel no evalúa los archivos
WORKSPACE
de ninguna dependencia, por lo que todas las dependencias transitivas deben definirse en el archivoWORKSPACE
del repositorio principal, además de las dependencias directas. - Para solucionar este problema, los proyectos adoptaron el patrón “deps.bzl”, en el que definen una macro que, a su vez, define varios repositorios y les pide a los usuarios que llamen a esta macro en sus archivos
WORKSPACE
.- Esto tiene sus propios problemas: las macros no pueden
load
otros archivos.bzl
, por lo que estos proyectos deben definir sus dependencias transitivas en esta macro "deps" o solucionar este problema haciendo que el usuario llame a varias macros "deps" en capas. - Bazel evalúa el archivo
WORKSPACE
de forma secuencial. Además, las dependencias se especifican conhttp_archive
con URLs, sin información de versión. Esto significa que no hay una forma confiable de realizar la resolución de versiones en el caso de las dependencias de diamante (A
depende deB
yC
;B
yC
dependen de diferentes versiones deD
).
- Esto tiene sus propios problemas: las macros no pueden
Debido a las deficiencias de WORKSPACE, el nuevo sistema basado en módulos (con el nombre en código “Bzlmod”) reemplazó gradualmente el sistema WORKSPACE heredado entre Bazel 6 y 9. Lee la guía de migración de Bzlmod para saber cómo migrar a Bzlmod.