Descripción general de las dependencias externas

Bazel admite dependencias externas, archivos fuente (tanto de texto como binarios) que se usan en tu compilación y que no provienen de tu espacio de trabajo. Por ejemplo, podrían 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 externas de Bazel funciona en función de los módulos de Bazel, cada uno de los cuales es un proyecto de Bazel versionado, 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 trabajas. Al igual que todos los módulos, debe tener un archivo MODULE.bazel en la raíz de su directorio, en el que se declaren sus metadatos básicos y dependencias directas. A continuación, se muestra 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")

A partir de ahí, 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 la versión, en la que se selecciona una versión para cada módulo, Bazel vuelve a consultar el registro para saber cómo definir un repo 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, se trata de archivos descargados de Internet y extraídos.

Los módulos también pueden especificar fragmentos de datos personalizados llamados etiquetas, que las extensiones de módulos consumen después de la resolución del módulo para definir repositorios adicionales. Estas extensiones pueden realizar acciones como E/S de archivos y enviar solicitudes de red. Entre otras cosas, permiten que Bazel interactúe con otros sistemas de administración de paquetes y, al mismo tiempo, respetan el gráfico de dependencias creado a partir de los módulos de Bazel.

Los tres tipos de repositorios (el principal, que es el árbol de origen en el que trabajas; los que representan módulos de dependencia transitiva y los que crean las extensiones de módulos) forman el espacio de trabajo. Los repositorios externos (que no son los principales) se recuperan a pedido, por ejemplo, cuando se hace referencia a ellos con etiquetas (como @repo//pkg:target) en los archivos BUILD.

Beneficios

El sistema de dependencias externas de Bazel ofrece una amplia variedad de beneficios.

Resolución de dependencia automática

  • Resolución de versiones determinística: Bazel adopta el algoritmo de resolución de versiones MVS determinístico, lo que minimiza los conflictos y aborda los problemas de dependencias en forma de diamante.
  • Administración de dependencias simplificada: MODULE.bazel declara solo las 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 estricta de las dependencias: Solo se ven las dependencias directas, lo que garantiza la corrección 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 el BCR, se optimiza su integración para toda la comunidad y se eliminan los esfuerzos duplicados y los conflictos de los archivos BUILD 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:

Funciones avanzadas

  • Extensiones de módulos: Las funciones de use_repo_rule y de extensión de módulos permiten el uso flexible de reglas de repositorios personalizadas y lógica de resolución para introducir cualquier dependencia que no sea de Bazel.
  • Comando bazel mod: El subcomando ofrece formas eficaces de inspeccionar las dependencias externas. Sabes exactamente cómo se define una dependencia externa y de dónde proviene.
  • Modo de proveedor: Obtén 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) Constancias de 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 espacio de trabajo local de Bazel, un módulo se representa con un repositorio.

Para obtener más detalles, consulta Módulos de Bazel.

Repositorio

Es 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 del repo puede ser MODULE.bazel (lo que indica que este repo representa un módulo de Bazel), REPO.bazel (consulta a continuación) o, en contextos heredados, WORKSPACE o WORKSPACE.bazel. Cualquier archivo marcador de límite del repo indicará el límite de un repo. Pueden coexistir varios archivos de este tipo en un directorio.

Repositorio principal

Es el repositorio en el que se ejecuta el comando de Bazel actual.

La raíz del repositorio principal también se conoce como la 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 repo principal y el conjunto de todos los repos externos definidos.

Ten en cuenta que, históricamente, los conceptos de "repositorio" y "espacio de trabajo" se han fusionado. El término "espacio de trabajo" se ha usado a menudo para referirse al repositorio principal y, a veces, incluso como sinónimo de "repositorio".

Nombre canónico del repositorio

Es el nombre por el que siempre se puede direccionar un repositorio. En el contexto de un espacio de trabajo, cada repositorio tiene un solo nombre canónico. Se puede hacer referencia a un destino dentro de un repo cuyo nombre canónico es canonical_name con la etiqueta @@canonical_name//package:target (ten en cuenta el doble @).

El repositorio principal siempre tiene la cadena vacía como nombre canónico.

Nombre aparente del repositorio

Es el nombre con el que se puede direccionar un repositorio en el contexto de otro repositorio determinado. Se puede considerar como el "apodo" de un repo: el repo con el nombre canónico michael podría tener el nombre aparente mike en el contexto del repo alice, pero podría tener el nombre aparente mickey en el contexto del repo bob. En este caso, se puede hacer referencia a un destino dentro de michael con la etiqueta @mike//package:target en el contexto de alice (ten en cuenta el único @).

A la inversa, esto se puede entender como una asignación de repositorios: cada repositorio mantiene una asignación del "nombre aparente del repositorio" a un "nombre canónico del repositorio".

Regla del repositorio

Es un esquema para las definiciones de repositorios que le indica a Bazel cómo materializar un repositorio. Por ejemplo, podría ser "descargar un archivo ZIP desde una URL determinada y extraerlo", "recuperar un artefacto de Maven determinado y ponerlo a disposición como un destino de java_import" o simplemente "crear un vínculo simbólico a un directorio local". Cada repo se define llamando a una regla de repo con una cantidad adecuada de argumentos.

Consulta Reglas del repositorio para obtener más información sobre cómo escribir tus propias reglas del repositorio.

Las reglas de repo más comunes son, por lejos, http_archive, que descarga un archivo desde una URL y lo extrae, y local_repository, que crea un vínculo simbólico a un directorio local que ya es un repositorio de Bazel.

Cómo recuperar un repositorio

Es la acción de hacer que un repo esté disponible en el disco local ejecutando su regla de repo asociada. Los repositorios definidos en un espacio de trabajo no están disponibles en el disco local antes de que se recuperen.

Normalmente, Bazel solo recupera un repo cuando necesita algo de él y el repo aún no se recuperó. Si el repo ya se recuperó antes, Bazel solo lo vuelve a recuperar si cambió su definición.

El comando fetch se puede usar para iniciar una recuperación previa de un repositorio, un destino o todos los repositorios necesarios para realizar cualquier compilación. Esta capacidad permite 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 utilizará cualquier versión almacenada en caché de la dependencia y, si no existe ninguna, el comando fallará.

Consulta las opciones de recuperación para obtener más información sobre cómo controlar la recuperación.

Diseño del directorio

Después de recuperarse, el repo se puede encontrar en el subdirectorio external de 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 repo. No es necesario que contenga nada para servir como 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 repo.

Por ejemplo, puedes especificar una licencia común para todos los destinos de tu repo con el siguiente archivo REPO.bazel:

repo(
    default_package_metadata = ["//:my_license"],
)

El sistema WORKSPACE heredado

En versiones anteriores de Bazel (anteriores a la 9.0), las dependencias externas se introducían definiendo repositorios en el archivo WORKSPACE (o WORKSPACE.bazel). Este archivo tiene una sintaxis similar a la de los archivos BUILD, y utiliza reglas de repo en lugar de reglas de compilación.

El siguiente fragmento es un ejemplo para usar la regla del 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 repo cuyo nombre canónico es foo. En el sistema WORKSPACE, de forma predeterminada, el nombre canónico de un repo también es su nombre aparente para todos los demás repos.

Consulta la lista completa de funciones disponibles en los archivos WORKSPACE.

Deficiencias del sistema WORKSPACE

En los años posteriores a la introducción del sistema WORKSPACE, los usuarios informaron muchos problemas, incluidos los siguientes:

  • Bazel no evalúa los archivos WORKSPACE de ninguna dependencia, por lo que todas las dependencias transitivas deben definirse en el archivo WORKSPACE 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 piden 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 con http_archive y URLs, sin información de versión. Esto significa que no hay una forma confiable de resolver la versión en el caso de las dependencias de diamante (A depende de B y C; B y C dependen de diferentes versiones de D).

Debido a las deficiencias de WORKSPACE, el nuevo sistema basado en módulos (cuyo nombre interno es "Bzlmod") reemplazó gradualmente el sistema heredado de WORKSPACE entre las versiones 6 y 9 de Bazel. Lee la guía de migración de Bzlmod para saber cómo migrar a Bzlmod.