Compila programas con Bazel

Informar un problema Ver fuente Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

En esta página, se explica cómo compilar un programa con Bazel, la sintaxis del comando de compilación y la sintaxis del patrón de destino.

Guía de inicio rápido

Para ejecutar Bazel, ve al directorio base de tu espacio de trabajo o a cualquiera de sus subdirectorios y escribe bazel. Consulta build si necesitas crear un lugar de trabajo nuevo.

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

Comandos disponibles:

  • analyze-profile: Analiza los datos del perfil de compilación.
  • aquery: Ejecuta una consulta en el gráfico de acción posterior al análisis.
  • build: Compila los objetivos especificados.
  • canonicalize-flags: Se convierten en canónicas las marcas de Bazel.
  • clean: Quita los archivos de salida y, de manera opcional, detiene el servidor.
  • cquery: Ejecuta una consulta del gráfico de dependencias del análisis posterior.
  • dump: Vuelca el estado interno del proceso del servidor de Bazel.
  • help: Imprime ayuda para los comandos o el índice.
  • info: Muestra información del tiempo de ejecución sobre el servidor de Bazel.
  • fetch: Recupera todas las dependencias externas de un destino.
  • mobile-install: Instala apps en dispositivos móviles.
  • query: Ejecuta una consulta de gráfico de dependencias.
  • run: Ejecuta el destino especificado.
  • shutdown: Detiene el servidor de Bazel.
  • test: Compila y ejecuta los destinos de prueba especificados.
  • version: Imprime información de la versión de Bazel.

Cómo obtener ayuda

  • bazel help command: Imprime ayuda y opciones para command.
  • bazel helpstartup_options: Son las opciones para la JVM que aloja Bazel.
  • bazel helptarget-syntax: Explica la sintaxis para especificar objetivos.
  • bazel help info-keys: Muestra una lista de las claves que usa el comando info.

La herramienta bazel realiza muchas funciones, llamadas comandos. Los más comunes son bazel build y bazel test. Puedes explorar los mensajes de ayuda en línea con bazel help.

Cómo compilar un objetivo

Antes de comenzar una compilación, necesitas un espacio de trabajo. Un espacio de trabajo es un árbol de directorios que contiene todos los archivos fuente necesarios para compilar tu aplicación. Bazel te permite realizar una compilación desde un volumen completamente de solo lectura.

Para compilar un programa con Bazel, escribe bazel build seguido del destino que deseas compilar.

bazel build //foo

Después de ejecutar el comando para compilar //foo, verás un resultado similar al siguiente:

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

Primero, Bazel carga todos los paquetes en el gráfico de dependencias de tu destino. Esto incluye las dependencias declaradas, los archivos que se enumeran directamente en el archivo BUILD del destino y las dependencias transitivas, los archivos que se enumeran en los archivos BUILD de las dependencias del destino. Después de identificar todas las dependencias, Bazel las analiza para verificar su corrección y crea las acciones de compilación. Por último, Bazel ejecuta los compiladores y otras herramientas de la compilación.

Durante la fase de ejecución de la compilación, Bazel imprime mensajes de progreso. Los mensajes de progreso incluyen el paso de compilación actual (como el compilador o el vinculador) cuando se inicia, y la cantidad de acciones de compilación completadas sobre la cantidad total. A medida que comienza la compilación, la cantidad de acciones totales suele aumentar a medida que Bazel descubre todo el gráfico de acciones, pero la cantidad se estabiliza en unos segundos.

Al final de la compilación, Bazel imprime qué destinos se solicitaron, si se compilaron correctamente o no y, si es así, dónde se pueden encontrar los archivos de salida. Las secuencias de comandos que ejecutan compilaciones pueden analizar de forma confiable este resultado. Consulta --show_result para obtener más detalles.

Si vuelves a escribir el mismo comando, la compilación finalizará mucho más rápido.

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

Esta es una compilación nula. Como no se realizaron cambios, no hay paquetes para volver a cargar ni pasos de compilación para ejecutar. Si algo cambió en "foo" o en sus dependencias, Bazel volvería a ejecutar algunas acciones de compilación o completaría una compilación incremental.

Cómo compilar varios objetivos

Bazel permite especificar los destinos que se compilarán de varias maneras. En conjunto, se conocen como patrones objetivo. Esta sintaxis se usa en comandos como build, test o query.

Mientras que las etiquetas se usan para especificar destinos individuales, como para declarar dependencias en archivos BUILD, los patrones de destino de Bazel especifican varios destinos. Los patrones de destino son una generalización de la sintaxis de etiquetas para conjuntos de destinos, con comodines. En el caso más simple, cualquier etiqueta válida también es un patrón de destino válido, que identifica un conjunto de exactamente un destino.

Todos los patrones de destino que comienzan con // se resuelven en relación con el espacio de trabajo actual.

//foo/bar:wiz Solo el objetivo único //foo/bar:wiz.
//foo/bar Equivale a //foo/bar:bar.
//foo/bar:all Son todos los destinos de reglas en el paquete foo/bar.
//foo/... Todos los destinos de reglas en todos los paquetes debajo del directorio foo.
//foo/...:all Todos los destinos de reglas en todos los paquetes debajo del directorio foo.
//foo/...:* Todos los destinos (reglas y archivos) en todos los paquetes debajo del directorio foo.
//foo/...:all-targets Todos los destinos (reglas y archivos) en todos los paquetes debajo del directorio foo.
//... Son todos los destinos de reglas en los paquetes del repositorio principal. No incluye destinos de repositorios externos.
//:all Todos los destinos de reglas en el paquete de nivel superior, si hay un archivo "BUILD" en la raíz del espacio de trabajo

Los patrones de destino que no comienzan con // se resuelven en relación con el directorio de trabajo actual. En estos ejemplos, se supone que el directorio de trabajo es foo:

:foo Equivale a //foo:foo.
bar:wiz Equivale a //foo/bar:wiz.
bar/wiz Equivale a lo siguiente:
  • //foo/bar/wiz:wiz si foo/bar/wiz es un paquete
  • //foo/bar:wiz si foo/bar es un paquete
  • De lo contrario, //foo:bar/wiz
bar:all Equivale a //foo/bar:all.
:all Equivale a //foo:all.
...:all Equivale a //foo/...:all.
... Equivale a //foo/...:all.
bar/...:all Equivale a //foo/bar/...:all.

De forma predeterminada, se siguen los vínculos simbólicos de directorios para los patrones de destino recursivos, excepto los que apuntan a la base de salida, como los vínculos simbólicos de conveniencia que se crean en el directorio raíz del espacio de trabajo.

Además, Bazel no sigue vínculos simbólicos cuando evalúa patrones de destino recursivos en ningún directorio que contenga un archivo con el siguiente nombre: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/... es un comodín sobre packages, que indica todos los paquetes de forma recursiva debajo del directorio foo (para todas las raíces de la ruta del paquete). :all es un comodín para los destinos que coincide con todas las reglas dentro de un paquete. Estos dos comodines se pueden combinar, como en foo/...:all, y, cuando se usan ambos, se pueden abreviar como foo/....

Además, :* (o :all-targets) es un comodín que coincide con todos los destinos en los paquetes coincidentes, incluidos los archivos que normalmente no se compilan con ninguna regla, como los archivos _deploy.jar asociados con las reglas java_binary.

Esto implica que :* denota un superconjunto de :all. Si bien puede ser confusa, esta sintaxis permite que el comodín :all conocido se use para compilaciones típicas, en las que no se desean destinos de compilación como _deploy.jar.

Además, Bazel permite usar una barra en lugar de los dos puntos que requiere la sintaxis de la etiqueta, lo que suele ser conveniente cuando se usa la expansión de nombres de archivos de Bash. Por ejemplo, foo/bar/wiz es equivalente a //foo/bar:wiz (si hay un paquete foo/bar) o a //foo:bar/wiz (si hay un paquete foo).

Muchos comandos de Bazel aceptan una lista de patrones de destino como argumentos, y todos respetan el operador de negación de prefijo -. Se puede usar para restar un conjunto de objetivos del conjunto especificado por los argumentos anteriores. Ten en cuenta que esto significa que el orden es importante. Por ejemplo:

bazel build foo/... bar/...

significa "compila todos los destinos debajo de foo y todos los destinos debajo de bar", mientras que

bazel build -- foo/... -foo/bar/...

significa "compila todos los destinos debajo de foo excepto los que están debajo de foo/bar". (El argumento -- es obligatorio para evitar que los argumentos posteriores que comienzan con - se interpreten como opciones adicionales).

Sin embargo, es importante señalar que restar destinos de esta manera no garantiza que no se compilen, ya que pueden ser dependencias de destinos que no se restaron. Por ejemplo, si hubiera un destino //foo:all-apis que, entre otros, dependiera de //foo/bar:api, este último se compilaría como parte de la compilación del primero.

Los destinos con tags = ["manual"] no se incluyen en los patrones de destino con comodín (..., :*, :all, etc.) cuando se especifican en comandos como bazel build y bazel test (pero sí se incluyen en los patrones de destino con comodín negativos, es decir, se restarán). Si quieres que Bazel los compile o pruebe, debes especificar esos destinos de prueba con patrones de destino explícitos en la línea de comandos. En cambio, bazel query no realiza ningún filtrado de este tipo automáticamente (eso anularía el propósito de bazel query).

Recuperación de dependencias externas

De forma predeterminada, Bazel descargará y creará vínculos simbólicos a las dependencias externas durante la compilación. Sin embargo, esto puede ser indeseable, ya sea porque te gustaría saber cuándo se agregan nuevas dependencias externas o porque te gustaría "prefetch" las dependencias (por ejemplo, antes de un vuelo en el que estarás sin conexión). Si deseas evitar que se agreguen dependencias nuevas durante las compilaciones, puedes especificar la marca --fetch=false. Ten en cuenta que esta marca solo se aplica a las reglas del repositorio que no apuntan a un directorio en el sistema de archivos local. Los cambios, por ejemplo, en local_repository, new_local_repository y las reglas del repositorio del SDK y el NDK de Android siempre entrarán en vigencia independientemente del valor --fetch .

Si no permites la recuperación durante las compilaciones y Bazel encuentra nuevas dependencias externas, la compilación fallará.

Puedes recuperar dependencias de forma manual ejecutando bazel fetch. Si no permites la recuperación durante la compilación, deberás ejecutar bazel fetch:

  • Antes de compilar por primera vez
  • Después de agregar una nueva dependencia externa

Una vez que se ejecute, no deberías tener que volver a ejecutarlo hasta que cambie el archivo MODULE.bazel.

fetch toma una lista de destinos para los que se deben recuperar las dependencias. Por ejemplo, esto recuperaría las dependencias necesarias para compilar //foo:bar y //bar:baz:

bazel fetch //foo:bar //bar:baz

Para recuperar todas las dependencias externas de un espacio de trabajo, ejecuta lo siguiente:

bazel fetch //...

Con Bazel 7 o versiones posteriores, si tienes habilitado Bzlmod, también puedes recuperar todas las dependencias externas ejecutando el siguiente comando:

bazel fetch

No es necesario que ejecutes bazel fetch si tienes todas las herramientas que usas (desde los archivos JAR de la biblioteca hasta el JDK) en la raíz de tu espacio de trabajo. Sin embargo, si usas algo fuera del directorio del espacio de trabajo, Bazel ejecutará automáticamente bazel fetch antes de ejecutar bazel build.

La caché del repositorio

Bazel intenta evitar recuperar el mismo archivo varias veces, incluso si se necesita el mismo archivo en diferentes espacios de trabajo o si cambió la definición de un repositorio externo, pero aún necesita descargar el mismo archivo. Para ello, Bazel almacena en caché todos los archivos descargados en la caché del repositorio que, de forma predeterminada, se encuentra en ~/.cache/bazel/_bazel_$USER/cache/repos/v1/. La ubicación se puede cambiar con la opción --repository_cache. La caché se comparte entre todos los espacios de trabajo y las versiones instaladas de Bazel. Se toma una entrada de la caché si Bazel sabe con certeza que tiene una copia del archivo correcto, es decir, si la solicitud de descarga tiene especificada una suma SHA256 del archivo y hay un archivo con ese hash en la caché. Por lo tanto, especificar un hash para cada archivo externo no solo es una buena idea desde el punto de vista de la seguridad, sino que también ayuda a evitar descargas innecesarias.

Con cada acierto de caché, se actualiza la hora de modificación del archivo en la caché. De esta manera, se puede determinar fácilmente el último uso de un archivo en el directorio de caché, por ejemplo, para limpiar la caché de forma manual. La caché nunca se limpia automáticamente, ya que puede contener una copia de un archivo que ya no está disponible en la fuente.

[Obsoleto] Directorios de archivos de distribución

Obsoleto: Se prefiere usar la caché del repositorio para lograr una compilación sin conexión.

El directorio de distribución es otro mecanismo de Bazel para evitar descargas innecesarias. Bazel busca en los directorios de distribución antes de buscar en la caché del repositorio. La principal diferencia es que el directorio de distribución requiere una preparación manual.

Con la opción --distdir=/path/to-directory, puedes especificar directorios adicionales de solo lectura para buscar archivos en lugar de recuperarlos. Se toma un archivo de ese directorio si el nombre del archivo es igual al nombre base de la URL y, además, el hash del archivo es igual al especificado en la solicitud de descarga. Esto solo funciona si el hash del archivo se especifica en la declaración de la regla del repo.

Si bien la condición del nombre de archivo no es necesaria para la corrección, reduce la cantidad de archivos candidatos a uno por directorio especificado. De esta manera, especificar directorios de archivos de distribución sigue siendo eficiente, incluso si la cantidad de archivos en un directorio de este tipo aumenta considerablemente.

Cómo ejecutar Bazel en un entorno aislado

Para mantener pequeño el tamaño del binario de Bazel, las dependencias implícitas de Bazel se recuperan a través de la red cuando se ejecuta por primera vez. Estas dependencias implícitas contienen cadenas de herramientas y reglas que pueden no ser necesarias para todos. Por ejemplo, las herramientas de Android se separan y se recuperan solo cuando se compilan proyectos de Android.

Sin embargo, estas dependencias implícitas pueden causar problemas cuando se ejecuta Bazel en un entorno aislado, incluso si ya incorporaste todas tus dependencias externas. Para solucionar este problema, puedes preparar una caché de repositorio (con Bazel 7 o versiones posteriores) o un directorio de distribución (con Bazel anterior a la versión 7) que contenga estas dependencias en una máquina con acceso a la red y, luego, transferirlas al entorno aislado con un enfoque sin conexión.

Caché del repositorio (con Bazel 7 o versiones posteriores)

Para preparar la caché del repositorio, usa la marca --repository_cache. Deberás hacerlo una vez por cada versión binaria nueva de Bazel, ya que las dependencias implícitas pueden ser diferentes para cada versión.

Para recuperar esas dependencias fuera de tu entorno aislado, primero crea un espacio de trabajo vacío:

mkdir empty_workspace && cd empty_workspace
touch MODULE.bazel

Para recuperar las dependencias integradas de Bzlmod, ejecuta el siguiente comando:

bazel fetch --repository_cache="path/to/repository/cache"

Si aún dependes del archivo WORKSPACE heredado, ejecuta el siguiente comando para recuperar las dependencias integradas de WORKSPACE:

bazel sync --repository_cache="path/to/repository/cache"

Por último, cuando uses Bazel en tu entorno aislado, pasa la misma marca --repository_cache. Para mayor comodidad, puedes agregarlo como una entrada .bazelrc:

common --repository_cache="path/to/repository/cache"

Además, es posible que también debas clonar el BCR de forma local y usar la marca --registry para que tu copia local apunte a él y evitar que Bazel acceda al BCR a través de Internet. Agrega la siguiente línea a tu .bazelrc:

common --registry="path/to/local/bcr/registry"
Directorio de distribución (con Bazel anterior a la versión 7)

Para preparar el directorio de distribución, usa la marca --distdir. Deberás hacerlo una vez por cada versión binaria nueva de Bazel, ya que las dependencias implícitas pueden ser diferentes para cada versión.

Para compilar estas dependencias fuera de tu entorno aislado, primero confirma el árbol de fuentes de Bazel en la versión correcta:

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

Luego, compila el archivo .tar.gz que contiene las dependencias de tiempo de ejecución implícitas para esa versión específica de Bazel:

bazel build @additional_distfiles//:archives.tar

Exporta este archivo .tar.gz a un directorio que se pueda copiar en tu entorno aislado. Ten en cuenta la marca --strip-components, ya que --distdir puede ser bastante quisquilloso con el nivel de anidamiento del directorio:

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

Por último, cuando uses Bazel en tu entorno aislado, pasa la marca --distdir que apunta al directorio. Para mayor comodidad, puedes agregarlo como una entrada .bazelrc:

build --distdir=path/to/directory

Configuraciones de compilación y compilación cruzada

Todas las entradas que especifican el comportamiento y el resultado de una compilación determinada se pueden dividir en dos categorías distintas. El primer tipo es la información intrínseca almacenada en los archivos BUILD de tu proyecto: la regla de compilación, los valores de sus atributos y el conjunto completo de sus dependencias transitivas. El segundo tipo son los datos externos o ambientales, proporcionados por el usuario o por la herramienta de compilación: la elección de la arquitectura de destino, las opciones de compilación y vinculación, y otras opciones de configuración de la cadena de herramientas. Nos referimos a un conjunto completo de datos ambientales como una configuración.

En cualquier compilación, puede haber más de una configuración. Considera una compilación cruzada, en la que compilas un ejecutable //foo:bin para una arquitectura de 64 bits, pero tu estación de trabajo es una máquina de 32 bits. Claramente, la compilación requerirá que se compile //foo:bin con una cadena de herramientas capaz de crear ejecutables de 64 bits, pero el sistema de compilación también debe compilar varias herramientas que se usan durante la compilación en sí (por ejemplo, herramientas que se compilan desde el código fuente y, luego, se usan en, por ejemplo, una genrule), y estas deben compilarse para ejecutarse en tu estación de trabajo. Por lo tanto, podemos identificar dos configuraciones: la configuración de ejecución, que se usa para compilar herramientas que se ejecutan durante la compilación, y la configuración de destino (o configuración de solicitud, pero decimos "configuración de destino" con más frecuencia, aunque esa palabra ya tenga muchos significados), que se usa para compilar el archivo binario que solicitaste en última instancia.

Por lo general, hay muchas bibliotecas que son requisitos previos del destino de compilación solicitado (//foo:bin) y de una o más herramientas de ejecución, por ejemplo, algunas bibliotecas básicas. Estas bibliotecas se deben compilar dos veces, una para la configuración de ejecución y otra para la configuración de destino. Bazel se encarga de garantizar que se compilen ambas variantes y que los archivos derivados se mantengan separados para evitar interferencias. Por lo general, estos destinos se pueden compilar de forma simultánea, ya que son independientes entre sí. Si ves mensajes de progreso que indican que un destino determinado se está compilando dos veces, es muy probable que esta sea la explicación.

La configuración de ejecución se deriva de la configuración de destino de la siguiente manera:

  • Usa la misma versión de Crosstool (--crosstool_top) que se especificó en la configuración de la solicitud, a menos que se especifique --host_crosstool_top.
  • Usa el valor de --host_cpu para --cpu (valor predeterminado: k8).
  • Usa los mismos valores de estas opciones que se especifican en la configuración de la solicitud: --compiler, --use_ijars y, si se usa --host_crosstool_top, el valor de --host_cpu se usa para buscar un default_toolchain en Crosstool (ignorando --compiler) para la configuración de ejecución.
  • Usa el valor de --host_javabase para --javabase.
  • Usa el valor de --host_java_toolchain para --java_toolchain.
  • Usa compilaciones optimizadas para el código C++ (-c opt).
  • No genera información de depuración (--copt=-g0).
  • Quita la información de depuración de los ejecutables y las bibliotecas compartidas (--strip=always).
  • Coloca todos los archivos derivados en una ubicación especial, distinta de la que usa cualquier configuración de solicitud posible.
  • Suprime el estampado de los archivos binarios con datos de compilación (consulta las opciones de --embed_*).
  • Todos los demás valores permanecen con su configuración predeterminada.

Existen muchos motivos por los que podría ser preferible seleccionar una configuración de ejecución distinta de la configuración de la solicitud. Lo más importante:

En primer lugar, al usar archivos binarios optimizados y despojados, se reduce el tiempo dedicado a vincular y ejecutar las herramientas, el espacio en disco que ocupan las herramientas y el tiempo de E/S de red en las compilaciones distribuidas.

En segundo lugar, al desacoplar las configuraciones de ejecución y de solicitud en todas las compilaciones, evitas las recompilaciones muy costosas que resultarían de cambios menores en la configuración de la solicitud (como cambiar una opción del vinculador), como se describió anteriormente.

Recompilaciones incrementales correctas

Uno de los objetivos principales del proyecto de Bazel es garantizar compilaciones incrementales correctas. Las herramientas de compilación anteriores, en especial las basadas en Make, realizan varias suposiciones incorrectas en su implementación de compilaciones incrementales.

En primer lugar, las marcas de tiempo de los archivos aumentan de forma monotónica. Si bien este es el caso típico, es muy fácil incumplir esta suposición: la sincronización con una revisión anterior de un archivo hace que disminuya la hora de modificación de ese archivo, y los sistemas basados en Make no volverán a compilar.

En general, si bien Make detecta cambios en los archivos, no detecta cambios en los comandos. Si modificas las opciones que se pasan al compilador en un paso de compilación determinado, Make no volverá a ejecutar el compilador, y será necesario descartar manualmente los resultados no válidos de la compilación anterior con make clean.

Además, Make no es robusto ante la finalización incorrecta de uno de sus subprocesos después de que este comienza a escribir en su archivo de salida. Si bien la ejecución actual de Make fallará, la invocación posterior de Make supondrá ciegamente que el archivo de salida truncado es válido (porque es más reciente que sus entradas) y no se volverá a compilar. Del mismo modo, si se detiene el proceso de compilación, puede ocurrir una situación similar.

Bazel evita estas suposiciones y otras. Bazel mantiene una base de datos de todo el trabajo realizado anteriormente y solo omitirá un paso de compilación si encuentra que el conjunto de archivos de entrada (y sus marcas de tiempo) para ese paso de compilación y el comando de compilación para ese paso de compilación coinciden exactamente con uno de la base de datos, y que el conjunto de archivos de salida (y sus marcas de tiempo) para la entrada de la base de datos coinciden exactamente con las marcas de tiempo de los archivos en el disco. Cualquier cambio en los archivos de entrada o salida, o en el comando en sí, provocará una nueva ejecución del paso de compilación.

El beneficio para los usuarios de las compilaciones incrementales correctas es que se pierde menos tiempo debido a la confusión. (Además, se dedica menos tiempo a esperar las recompilaciones causadas por el uso de make clean, ya sea necesario o preventivo).

Compilaciones incrementales y coherencia de compilación

Formalmente, definimos el estado de una compilación como coherente cuando existen todos los archivos de salida esperados y su contenido es correcto, según lo especificado por los pasos o las reglas necesarios para crearlos. Cuando editas un archivo fuente, se dice que el estado de la compilación es incoherente y sigue siendo incoherente hasta que vuelvas a ejecutar la herramienta de compilación y se complete correctamente. Describimos esta situación como incoherencia inestable, ya que es solo temporal y la coherencia se restablece cuando se ejecuta la herramienta de compilación.

Existe otro tipo de incoherencia que es perjudicial: la incoherencia estable. Si la compilación alcanza un estado inconsistente estable, la invocación correcta repetida de la herramienta de compilación no restablece la coherencia: la compilación se "bloqueó" y los resultados siguen siendo incorrectos. Los estados incoherentes estables son el motivo principal por el que los usuarios de Make (y otras herramientas de compilación) escriben make clean. Descubrir que la herramienta de compilación falló de esta manera (y luego recuperarse de ella) puede llevar mucho tiempo y ser muy frustrante.

En términos conceptuales, la forma más sencilla de lograr una compilación coherente es descartar todos los resultados de compilaciones anteriores y comenzar de nuevo: hacer que cada compilación sea una compilación limpia. Obviamente, este enfoque requiere demasiado tiempo para ser práctico (excepto tal vez para los ingenieros de versiones) y, por lo tanto, para que sea útil, la herramienta de compilación debe poder realizar compilaciones incrementales sin comprometer la coherencia.

El análisis de dependencias incremental correcto es difícil y, como se describió anteriormente, muchas otras herramientas de compilación no logran evitar estados inconsistentes estables durante las compilaciones incrementales. En cambio, Bazel ofrece la siguiente garantía: después de una invocación exitosa de la herramienta de compilación durante la cual no realizaste ninguna edición, la compilación estará en un estado coherente. (Si editas tus archivos fuente durante una compilación, Bazel no garantiza la coherencia del resultado de la compilación actual. Sin embargo, garantiza que los resultados de la próxima compilación restablecerán la coherencia.

Al igual que con todas las garantías, hay algunas condiciones: existen algunas formas conocidas de llegar a un estado incoherente estable con Bazel. No garantizamos que investigaremos los problemas que surjan de intentos deliberados de encontrar errores en el análisis de dependencias incremental, pero investigaremos y haremos todo lo posible para corregir todos los estados incoherentes estables que surjan del uso normal o "razonable" de la herramienta de compilación.

Si alguna vez detectas un estado incoherente estable con Bazel, informa un error.

Ejecución en zona de pruebas

Bazel usa zonas de pruebas para garantizar que las acciones se ejecuten de forma hermética y correcta. Bazel ejecuta spawns (en términos generales, acciones) en zonas de pruebas que solo contienen el conjunto mínimo de archivos que la herramienta requiere para realizar su trabajo. Actualmente, el aislamiento funciona en Linux 3.12 o versiones posteriores con la opción CONFIG_USER_NS habilitada, y también en macOS 10.11 o versiones posteriores.

Bazel imprimirá una advertencia si tu sistema no admite el aislamiento de zona de pruebas para alertarte sobre el hecho de que no se garantiza que las compilaciones sean herméticas y podrían afectar el sistema host de formas desconocidas. Para inhabilitar esta advertencia, puedes pasar la marca --ignore_unsupported_sandboxing a Bazel.

En algunas plataformas, como los nodos de clúster de Google Kubernetes Engine o Debian, los espacios de nombres de usuario se desactivan de forma predeterminada debido a problemas de seguridad. Para verificarlo, consulta el archivo /proc/sys/kernel/unprivileged_userns_clone: si existe y contiene un 0, los espacios de nombres del usuario se pueden activar con sudo sysctl kernel.unprivileged_userns_clone=1.

En algunos casos, el sandbox de Bazel no ejecuta reglas debido a la configuración del sistema. El síntoma suele ser una falla que genera un mensaje similar a namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory. En ese caso, intenta desactivar el entorno de pruebas para las reglas de genrules con --strategy=Genrule=standalone y para otras reglas con --spawn_strategy=standalone. También te pedimos que informes un error en nuestro sistema de seguimiento de problemas y menciones qué distribución de Linux usas para que podamos investigar y proporcionar una corrección en una versión posterior.

Fases de una compilación

En Bazel, una compilación se produce en tres fases distintas. Como usuario, comprender la diferencia entre ellas proporciona información sobre las opciones que controlan una compilación (consulta a continuación).

Fase de carga

La primera es la carga, durante la cual se cargan, analizan, evalúan y almacenan en caché todos los archivos BUILD necesarios para los destinos iniciales y su cierre transitivo de dependencias.

En la primera compilación después de que se inicia un servidor de Bazel, la fase de carga suele tardar muchos segundos, ya que se cargan muchos archivos BUILD desde el sistema de archivos. En las compilaciones posteriores, especialmente si no se modificó ningún archivo BUILD, la carga se produce muy rápido.

Los errores que se informan durante esta fase incluyen los siguientes: no se encontró el paquete, no se encontró el destino, errores léxicos y gramaticales en un archivo BUILD, y errores de evaluación.

Fase de análisis

La segunda fase, análisis, implica el análisis semántico y la validación de cada regla de compilación, la construcción de un gráfico de dependencias de compilación y la determinación exacta del trabajo que se debe realizar en cada paso de la compilación.

Al igual que la carga, el análisis también tarda varios segundos cuando se calcula en su totalidad. Sin embargo, Bazel almacena en caché el gráfico de dependencias de una compilación a la siguiente y solo vuelve a analizar lo que debe, lo que puede hacer que las compilaciones incrementales sean extremadamente rápidas en el caso de que los paquetes no hayan cambiado desde la compilación anterior.

Los errores que se informan en esta etapa incluyen los siguientes: dependencias inadecuadas, entradas no válidas para una regla y todos los mensajes de error específicos de la regla.

Las fases de carga y análisis son rápidas porque Bazel evita la E/S de archivos innecesaria en esta etapa, ya que solo lee los archivos BUILD para determinar el trabajo que se debe realizar. Esto es intencional y convierte a Bazel en una buena base para las herramientas de análisis, como el comando query de Bazel, que se implementa sobre la fase de carga.

Fase de ejecución

La tercera y última fase de la compilación es la ejecución. Esta fase garantiza que los resultados de cada paso de la compilación sean coherentes con sus entradas, y vuelve a ejecutar las herramientas de compilación, vinculación, etcétera, según sea necesario. En este paso, la compilación dedica la mayor parte de su tiempo, desde unos segundos hasta más de una hora para una compilación grande. Los errores que se informan durante esta fase incluyen los siguientes: falta de archivos fuente, errores en una herramienta ejecutada por alguna acción de compilación o falla de una herramienta para producir el conjunto esperado de resultados.