Preguntas frecuentes

En esta página, se responden algunas preguntas frecuentes sobre las dependencias externas en Bazel.

MODULE.bazel

¿Cómo debería versionar un módulo de Bazel?

Configurar version con la directiva module en el archivo de origen MODULE.bazel puede tener varias desventajas y efectos secundarios no deseados si no se administra con cuidado:

  • Duplicación: Por lo general, lanzar una versión nueva de un módulo implica tanto aumentar la versión en MODULE.bazel como etiquetar el lanzamiento, dos pasos separados que pueden desincronizarse. Si bien la automatización puede reducir este riesgo, es más sencillo y seguro evitarlo por completo.

  • Inconsistencia: Los usuarios que anulan un módulo con una confirmación específica mediante una anulación que no es de registro verán una versión incorrecta. Por ejemplo, si el MODULE.bazel en el archivo de origen establece version = "0.3.0", pero se realizaron confirmaciones adicionales desde ese lanzamiento, un usuario que anule con una de esas confirmaciones aún vería 0.3.0. En realidad, la versión debería reflejar que está por delante del lanzamiento, por ejemplo, 0.3.1-rc1.

  • Problemas de anulación que no son de registro: El uso de valores de marcador de posición puede causar problemas cuando los usuarios anulan un módulo con una anulación que no es de registro. Por ejemplo, 0.0.0 no se ordena como la versión más alta, que suele ser el comportamiento esperado que desean los usuarios cuando realizan una anulación que no es de registro.

Por lo tanto, es mejor evitar configurar la versión en el archivo de origen MODULE.bazel. En su lugar, configúrala en el MODULE.bazel almacenado en el registro (p.ej., el Registro central de Bazel), que es la fuente de información real para la versión del módulo durante la resolución de dependencias externas de Bazel (consulta Registros de Bazel).

Por lo general, esto se automatiza. Por ejemplo, el rules-template repositorio de reglas de ejemplo usa una acción de GitHub bazel-contrib/publish-to-bcr publish.yaml para publicar el lanzamiento en el BCR. La acción genera un parche para el archivo de origen MODULE.bazel con la versión de lanzamiento. Este parche se almacena en el registro y se aplica cuando se recupera el módulo durante la resolución de dependencias externas de Bazel.

De esta manera, la versión de los lanzamientos en el registro se establecerá correctamente en la versión lanzada y, por lo tanto, bazel_dep, single_version_override y multiple_version_override funcionarán como se espera, al tiempo que se evitan posibles problemas cuando se realiza una anulación que no es de registro, ya que la versión del archivo de origen será el valor predeterminado (''), que siempre se manejará correctamente (después de todo, es el valor predeterminado de la versión) y se comportará como se espera cuando se ordene (la cadena vacía se trata como la versión más alta).

¿Qué es un nivel de compatibilidad?

Debes dejar de usar compatibility_level.

Aumentar compatibility_level genera conflictos de versiones que son difíciles de resolver para los usuarios finales. Por lo tanto, a partir de Bazel 8.6.0 y 9.1.0, ambos compatibility_level y max_compatibility_level son operaciones no op.

Los mantenedores de módulos que introducen cambios rotundos importantes deben asegurarse de que las fallas de compilación proporcionen mensajes de error claros y rutas de migración prácticas.

Documentación heredada:

El compatibility_level de un módulo de Bazel debe aumentarse en la misma confirmación que introduce un cambio incompatible con versiones anteriores ("rotundo").

Sin embargo, Bazel puede mostrar un error si detecta que existen versiones del mismo módulo con diferentes niveles de compatibilidad en el gráfico de dependencias resuelto. Esto puede suceder cuando, por ejemplo, dos módulos dependen de versiones de un tercer módulo con diferentes niveles de compatibilidad.

Por lo tanto, aumentar compatibility_level con demasiada frecuencia puede ser muy perjudicial y no se recomienda. Para evitar esta situación, compatibility_level se debe aumentar solo cuando el cambio rotundo afecte a la mayoría de los casos de uso y no sea fácil de migrar o solucionar.

¿Por qué MODULE.bazel no admite loads?

Durante la resolución de dependencias, el archivo MODULE.bazel de todas las dependencias externas a las que se hace referencia se recupera de los registros. En esta etapa, aún no se recuperan los archivos de origen de las dependencias. Por lo tanto, si el archivo MODULE.bazel loads otro archivo, Bazel no podrá recuperar ese archivo sin recuperar todo el archivo de origen. Ten en cuenta que el archivo MODULE.bazel en sí es especial, ya que se aloja directamente en el registro.

Existen algunos casos de uso en los que las personas que solicitan loads en MODULE.bazel suelen estar interesadas, y se pueden resolver sin loads:

  • Asegurarse de que la versión que aparece en MODULE.bazel sea coherente con los metadatos de compilación almacenados en otro lugar, por ejemplo, en un archivo .bzl: Esto se puede lograr con el método native.module_version en un archivo .bzl cargado desde un archivo BUILD.
  • Dividir un archivo MODULE.bazel muy grande en secciones administrables, en especial para monorepos: El módulo raíz puede usar la include directiva para dividir su archivo MODULE.bazel en varios segmentos. Por el mismo motivo por el que no permitimos loads en archivos MODULE.bazel, include no se puede usar en módulos que no sean raíz.
  • Es posible que los usuarios del sistema WORKSPACE anterior recuerden declarar un repositorio y, luego, inmediatamente loading desde ese repositorio para realizar una lógica compleja. Esta capacidad se reemplazó por extensiones de módulo.

¿Puedo especificar un rango de SemVer para un bazel_dep?

No. Algunos otros administradores de paquetes, como npm y Cargo admiten rangos de versiones (implícita o explícitamente), y esto suele requerir un solucionador de restricciones (lo que dificulta la predicción del resultado para los usuarios) y hace que la resolución de versiones no sea reproducible sin un archivo de bloqueo.

En su lugar, Bazel usa la selección de versión mínima como Go, que, por el contrario, facilita la predicción del resultado y garantiza la reproducibilidad. Esta es una compensación que coincide con los objetivos de diseño de Bazel.

Además, las versiones de los módulos de Bazel son un superconjunto de SemVer, por lo que lo que tiene sentido en un entorno SemVer estricto no siempre se transfiere a las versiones de los módulos de Bazel.

¿Puedo obtener automáticamente la versión más reciente de un bazel_dep?

En ocasiones, algunos usuarios solicitan la capacidad de especificar bazel_dep(name = "foo", version = "latest") para obtener automáticamente la versión más reciente de una dependencia. Esto es similar a la pregunta sobre los rangos de SemVer, y la respuesta también es no.

La solución recomendada aquí es que la automatización se encargue de esto. Por ejemplo, Renovate admite módulos de Bazel.

A veces, los usuarios que hacen esta pregunta realmente buscan una forma de iterar rápidamente durante el desarrollo local. Esto se puede lograr con un local_path_override.

¿Por qué todos estos use_repos?

Los usos de la extensión del módulo en los archivos MODULE.bazel a veces vienen con una directiva grande use_repo. Por ejemplo, un uso típico de la go_deps extensión de gazelle podría verse de la siguiente manera:

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
    go_deps,
    "com_github_gogo_protobuf",
    "com_github_golang_mock",
    "com_github_golang_protobuf",
    "org_golang_x_net",
    ...  # potentially dozens of lines...
)

La directiva use_repo larga puede parecer redundante, ya que la información ya está en el archivo go.mod al que se hace referencia.

El motivo por el que Bazel necesita esta use_repo directiva es que ejecuta las extensiones de módulo de forma diferida. Es decir, una extensión de módulo solo se ejecuta si se observa su resultado. Como el "resultado" de una extensión de módulo son las definiciones de repositorio, esto significa que solo ejecutamos una extensión de módulo si se solicita un repositorio que define (por ejemplo, si se compila el destino @org_golang_x_net//:foo en el ejemplo anterior). Sin embargo, no sabemos qué repositorios definiría una extensión de módulo hasta después de ejecutarla. Aquí es donde entra en juego la directiva use_repo. El usuario puede indicarle a Bazel qué repositorios espera que genere la extensión, y Bazel solo ejecutará la extensión cuando se usen estos repositorios específicos.

Para ayudar a mantener esta directiva use_repo, una extensión de módulo puede mostrar un extension_metadata objeto desde su función de implementación. El usuario puede ejecutar el bazel mod tidy comando para actualizar las use_repo directivas de estas extensiones de módulo.

Migración de Bzlmod

¿Qué se evalúa primero, MODULE.bazel o WORKSPACE?

Cuando se configuran --enable_bzlmod y --enable_workspace, es natural preguntarse qué sistema se consulta primero. La respuesta breve es que MODULE.bazel (Bzlmod) se evalúa primero.

La respuesta larga es que "qué se evalúa primero" no es la pregunta correcta. En cambio, la pregunta correcta es: en el contexto del repositorio con el nombre canónico @@foo, ¿a qué se resuelve el nombre de repositorio aparente @bar? Como alternativa, ¿cuál es la asignación de repositorio de @@base?

Las etiquetas con nombres de repositorio aparentes (un solo @ inicial) pueden hacer referencia a diferentes elementos según el contexto desde el que se resuelven. Cuando ves una etiqueta @bar//:baz y te preguntas a qué apunta, primero debes averiguar cuál es el repositorio de contexto. Por ejemplo, si la etiqueta está en un archivo BUILD ubicado en el repositorio @@foo, el repositorio de contexto es @@foo.

Luego, según cuál sea el repositorio de contexto, se puede usar la tabla "Visibilidad del repositorio" en la guía de migración para averiguar a qué repositorio se resuelve realmente un nombre aparente.

  • Si el repositorio de contexto es el repositorio principal (@@):
    1. Si bar es un nombre de repositorio aparente que introduce el archivo MODULE.bazel del módulo raíz (a través de cualquiera de bazel_dep, use_repo, module, use_repo_rule), entonces @bar se resuelve en lo que afirma ese archivo MODULE.bazel.
    2. De lo contrario, si bar es un repositorio definido en WORKSPACE (lo que significa que su nombre canónico es @@bar), entonces @bar se resuelve en @@bar.
    3. De lo contrario, @bar se resuelve en algo como @@[unknown repo 'bar' requested from @@], y esto, en última instancia, generará un error.
  • Si el repositorio de contexto es un repositorio del mundo de Bzlmod (es decir, corresponde a un módulo de Bazel que no es raíz o se genera mediante una extensión de módulo), entonces solo verá otros repositorios del mundo de Bzlmod y no repositorios del mundo de WORKSPACE.
    • En particular, esto incluye cualquier repositorio que se introduzca en una extensión de módulo similar a non_module_depsen el módulo raíz o instancias use_repo_ruleen el módulo raíz.
  • Si el repositorio de contexto se define en WORKSPACE:
    1. Primero, verifica si la definición del repositorio de contexto tiene el atributo mágico repo_mapping Si es así, primero revisa la asignación (por lo que, para un repositorio definido con repo_mapping = {"@bar": "@baz"}, veríamos a @baz a continuación).
    2. Si bar es un nombre de repositorio aparente que introduce el archivo MODULE.bazel del módulo raíz, @bar se resuelve en lo que afirma ese archivo MODULE.bazel. (Esto es lo mismo que el elemento 1 en el caso del repositorio principal).
    3. De lo contrario, @bar se resuelve en @@bar. Lo más probable es que apunte a un repositorio bar definido en WORKSPACE. Si no se define ese repositorio, Bazel mostrará un error.

Para obtener una versión más concisa, haz lo siguiente:

  • Los repositorios del mundo de Bzlmod (excepto el repositorio principal) solo verán los repositorios del mundo de Bzlmod
  • Los repositorios del mundo de WORKSPACE (incluido el repositorio principal) primero verán lo que define el módulo raíz en el mundo de Bzlmod y, luego, volverán a ver los repositorios del mundo de WORKSPACE.

Cabe destacar que las etiquetas en la línea de comandos de Bazel (incluidas las marcas de Starlark, los valores de marca con tipo de etiqueta y los patrones de destino de compilación o prueba) se tratan como si tuvieran el repositorio principal como repositorio de contexto.

Otro

¿Cómo preparo y ejecuto una compilación sin conexión?

Usa el comando bazel fetch para recuperar repositorios. Puedes usar la marca --repo (como bazel fetch --repo @foo) para recuperar solo el repositorio @foo (resuelto en el contexto del repositorio principal, consulta la pregunta anterior) o usar un patrón de destino (como bazel fetch @foo//:bar) para recuperar todas las dependencias transitivas de @foo//:bar (esto equivale a bazel build --nobuild @foo//:bar).

Para asegurarte de que no se realicen recuperaciones durante una compilación, usa --nofetch. Más precisamente, esto hace que falle cualquier intento de ejecutar una regla de repositorio no local.

Si deseas recuperar repositorios y modificarlos para realizar pruebas de forma local, considera usar el bazel vendor comando.

¿Cómo aíslo mis compilaciones de Internet?

Estos son algunos sitios web en Internet de acceso público en los que se basa una compilación típica de Bazel y lo que puedes hacer para aislarte de posibles interrupciones, en especial como usuario empresarial de Bazel.

  • releases.bazel.build: Bazelisk descarga los objetos binarios de lanzamiento de Bazel desde este sitio web. Puedes configurar Bazelisk para que se descargue desde el duplicado interno de tu empresa.
  • bcr.bazel.build: Bazel consulta el BCR para obtener metadatos del módulo durante la resolución del módulo. Puedes duplicar el BCR y configurar la marca --registry para quitar tu dependencia de la infraestructura de servicio de BCR (consulta el aviso legal para obtener más información). Como alternativa, puedes asegurarte de que tu archivo MODULE.bazel.lock esté actualizado y configurar tus máquinas de CI o de desarrollador con una caché de descarga rellenada previamente (--repository_cache). Si se configura correctamente, la caché de descarga contendrá todos los archivos de registro necesarios para una compilación, y el archivo de bloqueo contendrá sus sumas de verificación. Luego, Bazel usará los resultados almacenados en caché y evitará el acceso a Internet durante la resolución del módulo.
  • mirror.bazel.build y github.com: Muchos módulos tienen archivos de origen alojados en estos dos sitios web. Considera configurar un duplicado interno de la empresa para los archivos de origen y usa --downloader_config o --module_mirrors para dirigir Bazel a ellos. Como alternativa, una caché de descarga rellenada previamente , como se mencionó en el punto anterior, también ayudará a Bazel a evitar por completo el acceso a Internet para los archivos de origen.

¿Cómo uso proxies HTTP?

Bazel respeta las variables de entorno http_proxy y HTTPS_PROXY que suelen aceptar otros programas, como curl.

¿Cómo hago que Bazel prefiera IPv6 en configuraciones de IPv4/IPv6 de pila doble?

En máquinas solo IPv6, Bazel puede descargar dependencias sin cambios. Sin embargo, en máquinas IPv4/IPv6 de pila doble, Bazel sigue la misma convención que Java, y prefiere IPv4 si está habilitado. En algunas situaciones, por ejemplo, cuando la red IPv4 no puede resolver o alcanzar direcciones externas, esto puede causar excepciones Network unreachable y fallas de compilación. En estos casos, puedes anular el comportamiento de Bazel para preferir IPv6 con la propiedad del sistema java.net.preferIPv6Addresses=true Específicamente, son las siguientes:

  • Usa la opción de inicio, por ejemplo, agregando la siguiente línea en tu archivo .bazelrc:--host_jvm_args=-Djava.net.preferIPv6Addresses=true

    startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true

  • Cuando ejecutes destinos de compilación de Java que necesiten conectarse a Internet (como para pruebas de integración), usa la --jvmopt=-Djava.net.preferIPv6Addresses=true marca de herramienta. Por ejemplo, incluye en tu .bazelrc archivo:

    build --jvmopt=-Djava.net.preferIPv6Addresses

  • Si usas rules_jvm_external para la resolución de la versión de dependencia, también agrega -Djava.net.preferIPv6Addresses=true a la COURSIER_OPTS variable de entorno para proporcionar opciones de JVM para Coursier.

¿Se pueden ejecutar reglas de repositorio de forma remota con la ejecución remota?

No, o al menos, todavía no. Es posible que los usuarios que emplean servicios de ejecución remota para acelerar sus compilaciones noten que las reglas de repositorio aún se ejecutan de forma local. Por ejemplo, primero se descargaría un http_archive en la máquina local (con cualquier caché de descarga local, si corresponde), se extraerá y, luego, cada archivo de origen se subiría al servicio de ejecución remota como un archivo de entrada. Es natural preguntarse por qué el servicio de ejecución remota no solo descarga y extrae ese archivo, lo que ahorra un viaje de ida y vuelta inútil.

Parte del motivo es que las reglas de repositorio (y las extensiones de módulo) son similares a "secuencias de comandos" que ejecuta Bazel. Un ejecutor remoto ni siquiera tiene instalado Bazel.

Otro motivo es que Bazel suele necesitar los archivos BUILD en los archivos descargados y extraídos para realizar la carga y el análisis, que se realizan de forma local.

Existen ideas preliminares para resolver este problema mediante la reimaginación de las reglas de repositorio como reglas de compilación, lo que, naturalmente, les permitiría ejecutarse de forma remota, pero, a la inversa, plantearía nuevas inquietudes arquitectónicas (por ejemplo, los query comandos podrían necesitar ejecutar acciones, lo que complicaría su diseño).

Para obtener más información sobre este tema, consulta Una forma de admitir repositorios que necesitan Bazel para recuperarse.