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?
Establecer version
con la directiva module
en el archivo fuente MODULE.bazel
puede tener varios inconvenientes y efectos secundarios no deseados si no se administra con cuidado:
Duplicación: Por lo general, lanzar una nueva versión de un módulo implica incrementar la versión en
MODULE.bazel
y 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 reemplacen un módulo con una confirmación específica usando un reemplazo no registrado verán una versión incorrecta. Por ejemplo, si el
MODULE.bazel
en el archivo fuente estableceversion = "0.3.0"
, pero se realizaron confirmaciones adicionales desde ese lanzamiento, un usuario que realice un reemplazo con una de esas confirmaciones seguirá viendo0.3.0
. En realidad, la versión debería reflejar que está adelantada a la versión de lanzamiento, por ejemplo,0.3.1-rc1
.Problemas de anulación no registrados: El uso de valores de marcador de posición puede causar problemas cuando los usuarios anulan un módulo con una anulación no registrada. Por ejemplo,
0.0.0
no se ordena como la versión más alta, que suele ser el comportamiento esperado que los usuarios desean cuando realizan una anulación no relacionada con el registro.
Por lo tanto, es mejor evitar establecer la versión en el archivo fuente 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 verdad 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 repositorio de reglas de ejemplo rules-template
usa una acción de GitHub de bazel-contrib/publish-to-bcr publish.yaml para publicar la versión en el BCR. La acción genera un parche para el archivo fuente 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 las versiones del registro se establecerá correctamente en la versión lanzada y, por lo tanto, bazel_dep
, single_version_override
y multiple_version_override
funcionarán según lo previsto, a la vez que se evitarán posibles problemas cuando se realice una anulación que no sea del registro, ya que la versión del archivo fuente será el valor predeterminado (''
), que siempre se controlará correctamente (después de todo, es el valor de la versión predeterminada) y se comportará según lo previsto al ordenar (la cadena vacía se trata como la versión más alta).
¿Cuándo debo incrementar el nivel de compatibilidad?
El compatibility_level
de un módulo de Bazel se debe incrementar en la misma confirmación que introduce un cambio incompatible con versiones anteriores ("rotundo").
Sin embargo, Bazel puede arrojar 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 ocurrir, por ejemplo, cuando dos módulos dependen de versiones de un tercer módulo con diferentes niveles de compatibilidad.
Por lo tanto, incrementar compatibility_level
con demasiada frecuencia puede ser muy perjudicial y no se recomienda. Para evitar esta situación, el compatibility_level
solo debe incrementarse cuando el cambio interruptivo 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 load
s?
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 fuente de las dependencias, por lo que, si el archivo MODULE.bazel load
s otro archivo, Bazel no puede recuperar ese archivo sin recuperar todo el archivo fuente. 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 load
s en MODULE.bazel suelen estar interesadas, y se pueden resolver sin load
s:
- Asegurarse de que la versión que aparece en MODULE.bazel coincida con los metadatos de compilación almacenados en otro lugar, por ejemplo, en un archivo .bzl: Esto se puede lograr usando 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 monorepositorios: El módulo raíz puede usar la directiva
include
para dividir su archivo MODULE.bazel en varios segmentos. Por el mismo motivo por el que no permitimosload
s en los archivos MODULE.bazel, no se puede usarinclude
en módulos que no sean raíz. - Es posible que los usuarios del sistema WORKSPACE anterior recuerden haber declarado un repo y, luego, haber realizado inmediatamente un
load
desde ese repo para ejecutar una lógica compleja. Esta capacidad se reemplazó por las extensiones de módulos.
¿Puedo especificar un rango de SemVer para un bazel_dep
?
No. Otros administradores de paquetes, como npm y Cargo, admiten rangos de versiones (de forma implícita o explícita), lo que a menudo requiere un solucionador de restricciones (lo que hace que el resultado sea más difícil de predecir para los usuarios) y hace que la resolución de versiones no sea reproducible sin un archivo de bloqueo.
En cambio, Bazel usa la selección de versión mínima, como Go, que, en contraste, hace que el resultado sea fácil de predecir 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 estricto de SemVer no siempre se aplica 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 objeto local_path_override
.
¿Por qué hay tantas use_repo
?
A veces, los usos de extensiones de módulos en archivos MODULE.bazel incluyen una directiva use_repo
grande. Por ejemplo, un uso típico de la extensión go_deps
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 directiva use_repo
es que ejecuta las extensiones de módulos de forma diferida. Es decir, una extensión de módulo solo se ejecuta si se observa su resultado. Dado que el "resultado" de una extensión de módulo son definiciones de repositorios, 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 del módulo hasta que la ejecutemos. 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 del módulo puede devolver un objeto extension_metadata
desde su función de implementación. El usuario puede ejecutar el comando bazel mod tidy
para actualizar las directivas use_repo
de estas extensiones de módulos.
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 "cuál se evalúa primero" no es la pregunta correcta, sino que la pregunta correcta es: en el contexto del repo con el nombre canónico @@foo
, ¿a qué se resuelve el nombre aparente del repo @bar
? Como alternativa, ¿cuál es la asignación del repositorio de @@base
?
Las etiquetas con nombres de repo evidentes (un solo @
inicial) pueden hacer referencia a diferentes cosas según el contexto desde el que se resuelven. Cuando ves una etiqueta @bar//:baz
y te preguntas a qué apunta en realidad, primero debes averiguar cuál es el repo de contexto. Por ejemplo, si la etiqueta está en un archivo BUILD ubicado en el repo @@foo
, el repo de contexto es @@foo
.
Luego, según el repositorio de contexto, se puede usar la tabla"visibilidad del repositorio" de la guía de migración para averiguar a qué repositorio se resuelve realmente un nombre aparente.
- Si el repositorio de contexto es el principal (
@@
):- Si
bar
es un nombre de repo aparente que se introdujo a través del archivo MODULE.bazel del módulo raíz (a través de cualquiera debazel_dep
,use_repo
,module
ouse_repo_rule
), entonces@bar
se resuelve en lo que declara ese archivo MODULE.bazel. - De lo contrario, si
bar
es un repo definido en WORKSPACE (lo que significa que su nombre canónico es@@bar
), entonces@bar
se resuelve como@@bar
. - De lo contrario,
@bar
se resuelve en algo como@@[unknown repo 'bar' requested from @@]
, lo que, en última instancia, generará un error.
- Si
- Si el repo de contexto es un repo del mundo de Bzlmod (es decir, corresponde a un módulo de Bazel no raíz o se genera a partir de una extensión de módulo), solo verá otros repos del mundo de Bzlmod y ningún repo del mundo de WORKSPACE.
- En particular, esto incluye cualquier repo que se introduzca en una extensión de módulo similar a
non_module_deps
en el módulo raíz o las instancias deuse_repo_rule
en el módulo raíz.
- En particular, esto incluye cualquier repo que se introduzca en una extensión de módulo similar a
- Si el repo de contexto se define en WORKSPACE, haz lo siguiente:
- Primero, verifica si la definición del repo de contexto tiene el atributo mágico
repo_mapping
. Si es así, primero revisa la asignación (por lo que, para un repositorio definido conrepo_mapping = {"@bar": "@baz"}
, veríamos@baz
a continuación). - Si
bar
es un nombre de repo aparente introducido por el archivo MODULE.bazel del módulo raíz,@bar
se resuelve en lo que declara ese archivo MODULE.bazel. (Es lo mismo que el elemento 1 en el caso del repositorio principal). - De lo contrario,
@bar
se resuelve como@@bar
. Lo más probable es que esto apunte a un repobar
definido en WORKSPACE. Si no se define tal repo, Bazel arrojará un error.
- Primero, verifica si la definición del repo de contexto tiene el atributo mágico
Para obtener una versión más concisa, haz lo siguiente:
- Los repositorios de Bzlmod-world (excepto el principal) solo verán los repositorios de Bzlmod-world.
- Los repositorios de WORKSPACE-world (incluido el repositorio principal) primero verán lo que define el módulo raíz en el mundo de Bzlmod y, luego, recurrirán a los repositorios de WORKSPACE-world.
Cabe destacar que las etiquetas en la línea de comandos de Bazel (incluidas las marcas de Starlark, los valores de marcas con tipo de etiqueta y los patrones de destino de compilación o prueba) se tratan como si tuvieran el repo principal como repo de contexto.
Otro
¿Cómo preparo y ejecuto una compilación sin conexión?
Usa el comando bazel fetch
para realizar la recuperación previa de repositorios. Puedes usar la marca --repo
(como bazel fetch --repo @foo
) para recuperar solo el repo @foo
(resuelto en el contexto del repo 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 quieres recuperar repositorios y modificarlos para realizar pruebas de forma local, considera usar el comando bazel vendor
.
¿Cómo uso los proxies HTTP?
Bazel respeta las variables de entorno http_proxy
y HTTPS_PROXY
que suelen aceptar otros programas, como curl.
¿Cómo hago para que Bazel prefiera IPv6 en configuraciones de pila doble IPv4/IPv6?
En las máquinas solo IPv6, Bazel puede descargar dependencias sin cambios. Sin embargo, en las máquinas de pila doble IPv4/IPv6, 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 de Network
unreachable
y fallas en la compilación. En estos casos, puedes anular el comportamiento de Bazel para que prefiera IPv6 con la propiedad del sistema java.net.preferIPv6Addresses=true
.
En particular, haz lo siguiente:
Usa la
--host_jvm_args=-Djava.net.preferIPv6Addresses=true
opción de inicio, por ejemplo, agregando la siguiente línea en tu archivo.bazelrc
: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 marca de herramienta
--jvmopt=-Djava.net.preferIPv6Addresses=true
. Por ejemplo, incluye lo siguiente en tu archivo.bazelrc
:build --jvmopt=-Djava.net.preferIPv6Addresses
Si usas
rules_jvm_external
para la resolución de versiones de dependencias, también agrega-Djava.net.preferIPv6Addresses=true
a la variable de entornoCOURSIER_OPTS
para proporcionar opciones de JVM para Coursier.
¿Se pueden ejecutar reglas de repo de forma remota con la ejecución remota?
No, o al menos no todavía. Es posible que los usuarios que emplean servicios de ejecución remota para acelerar sus compilaciones noten que las reglas del repositorio aún se ejecutan de forma local. Por ejemplo, un http_archive
se descargaría primero en la máquina local (con cualquier caché de descarga local, si corresponde), se extraerá y, luego, cada archivo fuente 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 descarga y extrae ese archivo, lo que ahorraría un viaje de ida y vuelta inútil.
Parte del motivo es que las reglas del repositorio (y las extensiones de módulos) son similares a "scripts" que ejecuta Bazel. Un ejecutor remoto ni siquiera tiene necesariamente instalado Bazel.
Otro motivo es que Bazel a menudo necesita 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, como reimaginar las reglas del repositorio como reglas de compilación, lo que permitiría que se ejecuten de forma remota, pero, a la vez, generaría nuevas inquietudes arquitectónicas (por ejemplo, los comandos query
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 que se recupere Bazel.