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 workspace
o a cualquiera de sus subdirectorios y escribe bazel
. Consulta compilación 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 acciones de postanálisis.build
: Compila los destinos especificados.canonicalize-flags
: Convierte en canónicas las marcas de Bazel.clean
: Quita los archivos de salida y, de manera opcional, detiene el servidor.cquery
: Ejecuta una consulta de grafo de dependencia posterior al análisis.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 entorno 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 dependencia.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 paracommand
.bazel help
startup_options
: Opciones para la JVM que aloja a Bazelbazel help
target-syntax
: Explica la sintaxis para especificar objetivos.bazel help info-keys
: Muestra una lista de las teclas que usa el comando info.
La herramienta bazel
realiza muchas funciones, llamadas comandos. Los más usados son bazel build
y bazel test
. Puedes navegar por los mensajes de ayuda en línea con bazel help
.
Cómo crear un destino
Antes de iniciar una compilación, necesitas un lugar 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 objetivo que deseas compilar.
bazel build //foo
Después de emitir 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 aparecen directamente en el archivo BUILD
del destino y las dependencias transitivas, los archivos que se enumeran en los archivos BUILD
de las dependencias del objetivo. Después de identificar todas las dependencias, Bazel las analiza para verificar que sean correctas 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 completada sobre la cantidad total de acciones de compilación. A medida que se inicia la compilación, la cantidad total de acciones suele aumentar a medida que Bazel descubre el gráfico de acciones completo, pero la cantidad se estabiliza en unos segundos.
Al final de la compilación, Bazel imprime qué destinos se solicitaron, si se compilaron de forma correcta o
no y, de ser así, dónde se pueden encontrar los
archivos de salida. Las secuencias de comandos que ejecutan compilaciones pueden analizar este resultado de manera confiable. Consulta --show_result
para obtener más información.
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 cambió nada, no hay paquetes para volver a cargar ni pasos de compilación para ejecutar. Si algo cambiaba en "foo" o sus dependencias, Bazel volvería a ejecutar algunas acciones de compilación o completaría una compilación incremental.
Cómo crear varios destinos
Bazel permite varias formas de especificar los destinos que se compilarán. En conjunto, se conocen como patrones de destino. Esta sintaxis se usa en comandos como
build
, test
o query
.
Mientras que las etiquetas se usan para especificar objetivos 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 objetivo.
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 |
Todos los objetivos de regla en el paquete foo/bar . |
//foo/... |
Todos los destinos de reglas en todos los paquetes debajo del directorio foo |
//foo/...:all |
Todas las reglas objetivo 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) de todos los paquetes del directorio foo |
//... |
Todos los destinos en paquetes del lugar de trabajo. Esto no incluye destinos de repositorios externos. |
//:all |
Todos los destinos en el paquete de nivel superior, si hay un archivo "Build" en la raíz del lugar 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:
|
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 symlinks de directorio para los patrones de destino recursivos, excepto aquellos que apuntan a la base de salida, como los symlinks de conveniencia que se crean en el directorio raíz del espacio de trabajo.
Además, Bazel no sigue los symlinks cuando evalúa patrones de destino recursivos en cualquier 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 manera recursiva debajo del directorio foo
(para todas las raíces de la ruta del paquete). :all
es un comodín sobre destinos que coincide con todas las reglas de un paquete. Estos dos se pueden combinar, como en foo/...:all
, y cuando se usan ambos comodines, esto se puede abreviar a 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 esta sintaxis puede ser confusa, permite usar el comodín :all
familiar para compilaciones típicas, en las que no se desean destinos de compilación como _deploy.jar
.
Además, Bazel permite que se use una barra en lugar de los dos puntos que requiere
la sintaxis de etiquetas; esto suele ser conveniente cuando se usa la expansión del nombre de archivo 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 del prefijo -
. Esto 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 importa. Por ejemplo:
bazel build foo/... bar/...
significa "crear todas las orientaciones inferiores a foo
y todas las orientaciones por debajo de bar
", mientras que
bazel build -- foo/... -foo/bar/...
significa "compilar 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 tener en cuenta que restar los objetivos de esta manera no garantiza que no se hayan compilado, ya que pueden ser dependencias de destinos que no se restaron. Por ejemplo, si hubiera un //foo:all-apis
objetivo 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 comodín
(...
, :*
, :all
, etc.) cuando se especifican en comandos como
bazel build
y bazel test
. Debes especificar estos
destinos de prueba con patrones de destino explícitos en la línea de comandos si quieres que Bazel
los compile o pruebe. En cambio, bazel query
no realiza ningún tipo de filtrado automáticamente (eso anularía el propósito de bazel query
).
Recupera dependencias externas
De forma predeterminada, Bazel descargará dependencias externas y realizará un symlink de dependencias durante la
compilación. Sin embargo, esto puede ser no deseado, ya sea porque quieres saber cuándo se agregan dependencias externas nuevas o porque quieres “cargar previamente” las dependencias (por ejemplo, antes de un vuelo en el que no tendrás conexión). Si quieres 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 de repositorio del SDK y NDK de Android siempre se aplicarán sin importar el valor --fetch
.
Si no permites la recuperación durante las compilaciones y Bazel encuentra dependencias externas nuevas, la compilación fallará.
Para recuperar dependencias de forma manual, ejecuta 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 dependencia externa nueva
Una vez ejecutada, no deberías necesitarla de nuevo hasta que el archivo WORKSPACE cambie.
fetch
toma una lista de destinos para los que se recuperarán 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 lugar de trabajo, ejecuta el siguiente comando:
bazel fetch //...
No necesitas ejecutar la recuperación de bazel 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 lugar 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 el mismo archivo para descargar. 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 lugares de trabajo y las versiones instaladas de Bazel.
Se toma una entrada de la caché si
Bazel sabe con seguridad que tiene una copia del archivo correcto, es decir, si la
solicitud de descarga tiene una suma SHA256 del archivo especificado y un archivo con ese
hash está en la caché. Por lo tanto, especificar un hash para cada archivo externo no solo es una buena idea desde una perspectiva de 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 con facilidad 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 sentido upstream.
Directorios de archivos de distribución
El directorio de distribución es otro mecanismo de Bazel que evita descargas innecesarias. Bazel busca directorios de distribución antes de la caché del repositorio. La diferencia principal es que el directorio de distribución requiere preparación manual.
Con la opción --distdir=/path/to-directory
, puedes especificar directorios de solo lectura adicionales 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 se especifica el hash del archivo en la declaración WORKSPACE.
Si bien la condición del nombre del archivo no es necesaria para que sea correcta, 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 aumenta la cantidad de archivos en ese directorio.
Cómo ejecutar Bazel en un entorno de aislamiento
Para mantener el tamaño binario de Bazel pequeño, las dependencias implícitas de Bazel se recuperan a través de la red mientras se ejecutan 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 descompilan y 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 suministraste todas tus dependencias de WORKSPACE. Para resolver eso, puedes preparar un directorio de distribución que contenga estas dependencias en una máquina con acceso a la red y, luego, transferirlas al entorno aislado en un aire sin conexión.
Para preparar el directorio de distribución, usa la marca --distdir
. Deberás hacerlo una vez por cada versión nueva del objeto binario 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 consulta 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 que contiene las dependencias implícitas del entorno de ejecución para esa versión específica de Bazel:
bazel build @additional_distfiles//:archives.tar
Exporta este tarball a un directorio que se pueda copiar en tu entorno sin conexión a Internet. Ten en cuenta la marca --strip-components
, ya que --distdir
puede ser bastante exigente con el nivel de anidación de directorios:
tar xvf bazel-bin/external/additional_distfiles/archives.tar \
-C "$NEW_DIRECTORY" --strip-components=3
Por último, cuando uses Bazel en tu entorno de aislamiento, pasa la marca --distdir
que apunte al directorio. Para mayor comodidad, puedes agregarla como una entrada .bazelrc
:
build --distdir=path/to/directory
Parámetros de configuración 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 del entorno que proporciona el usuario o 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 del entorno como una configuración.
En una compilación determinada, 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á compilar //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 la 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 host, que se usa para compilar herramientas que se ejecutan durante la compilación, y la configuración de destino (o la configuración de solicitud, pero decimos "configuración de destino" con mayor frecuencia, aunque esa palabra ya tiene muchos significados), que se usa para compilar el objeto 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 una o más de las herramientas de host, por ejemplo, algunas bibliotecas base. Esas bibliotecas se deben compilar dos veces, una para la configuración del host y otra para la configuración de destino. Bazel se encarga de
asegurarse de que se compilen ambas variantes y de que los archivos derivados se mantengan
separados para evitar interferencias. Por lo general, esos destinos se pueden compilar de forma simultánea,
ya que son independientes entre sí. Si ves mensajes de progreso que indican que se está compilando un destino determinado dos veces, es probable que esta sea la explicación.
Bazel usa uno de los dos métodos para seleccionar la configuración del host, según la
opción --distinct_host_configuration
. Esta opción booleana es un poco sutil, y la configuración puede mejorar (o empeorar) la velocidad de tus compilaciones.
--distinct_host_configuration=false
Cuando esta opción es falsa, las configuraciones de host y solicitud son idénticas: todas las herramientas necesarias durante la compilación se compilarán de la misma manera que los programas de destino. Esta configuración significa que no es necesario compilar bibliotecas dos veces durante una sola compilación.
Sin embargo, significa que cualquier cambio en la configuración de la solicitud también afecta la configuración del host, lo que causa que se vuelvan a compilar todas las herramientas y también se reconstruya todo lo que dependa del resultado de la herramienta. Por lo tanto, por ejemplo, el simple cambio de una opción del vinculador entre compilaciones podría hacer que se vuelvan a vincular todas las herramientas y, luego, se vuelvan a ejecutar todas las acciones que las usan, y así sucesivamente, lo que generaría una compilación muy grande.
--distinct_host_configuration=true
(predeterminado)
Si esta opción es verdadera, en lugar de usar la misma configuración para el host y la solicitud, se utiliza una configuración de host completamente distinta. La configuración del host se deriva de la configuración de destino de la siguiente manera:
- Usa la misma versión de Crosstool (
--crosstool_top
) que se especifica 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 undefault_toolchain
en Crosstool (ignorando--compiler
) para la configuración del host. - Usa el valor de
--host_javabase
para--javabase
. - Usa el valor de
--host_java_toolchain
para--java_toolchain
. - Usa compilaciones optimizadas para código C++ (
-c opt
). - No se 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 objetos binarios con datos de compilación (consulta las opciones de
--embed_*
). - Todos los demás valores permanecen en su configuración predeterminada.
Existen muchos motivos por los que podría ser preferible seleccionar una configuración de host distinta de la configuración de la solicitud. Algunos son demasiado esotéricos para mencionarlos aquí, pero vale la pena señalar dos de ellos.
En primer lugar, cuando usas objetos binarios optimizados y sin elementos innecesarios, reduces el tiempo que se dedica a vincular y ejecutar las herramientas, el espacio en el disco que ocupan las herramientas y el tiempo de E/S de red en compilaciones distribuidas.
En segundo lugar, si separas las configuraciones de host y de solicitud en todas las compilaciones, evitas recompilaciones muy costosas que resultarían de cambios menores en la configuración de la solicitud (como cambiar las opciones de un vinculador), como se describió anteriormente.
Dicho esto, para ciertas compilaciones, esta opción puede ser un obstáculo. En particular, las compilaciones en las que los cambios de configuración son poco frecuentes (en especial, ciertas compilaciones de Java) y las en las que la cantidad de código que se debe compilar en las configuraciones de host y de destino es grande pueden no ser beneficiosas.
Recompilaciones incrementales correctas
Uno de los objetivos principales del proyecto Bazel es garantizar que las recompilaciones incrementadas sean correctas. Las herramientas de compilación anteriores, en especial las basadas en Make, realizan varias suposiciones poco sólidas en la implementación de compilaciones incrementales.
En primer lugar, las marcas de tiempo de los archivos aumentan de forma monótona. Si bien este es el caso típico, es muy fácil caer en esta suposición. La sincronización con una revisión anterior de un archivo hace que disminuya el tiempo de modificación de ese archivo. Los sistemas basados en Make no se volverán a compilar.
En términos más generales, 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 es necesario descartar manualmente los resultados no válidos de la compilación anterior usando make clean
.
Además, Make no es resistente a la finalización fallida de uno de sus subprocesos después de que ese subproceso comenzó 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 nuevo que sus entradas) y no se volverá a compilar. Del mismo modo, si se finaliza el proceso de Make, 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 descubre que el conjunto de archivos de entrada (y sus marcas de tiempo) en ese paso de compilación y el comando de compilación para ese paso de compilación coinciden exactamente con uno en 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 coincide exactamente con las marcas de tiempo de los archivos en el disco. Cualquier cambio en los archivos de entrada o de salida, o en el comando en sí, provocará que se vuelva a ejecutar el paso de compilación.
El beneficio para los usuarios de las compilaciones incrementales correctas es: menos tiempo desperdiciado debido a la confusión. (Además, se reduce el tiempo de espera para las recompilaciones causadas por el uso de make
clean
, ya sea necesario o preventivo).
Coherencia de la compilación y compilaciones incrementales
De forma formal, definimos el estado de una compilación como coherente cuando existen todos los archivos de salida esperados y su contenido es correcto, como lo especifican los pasos o las reglas necesarios para crearlos. Cuando editas un archivo de origen, se dice que el estado de la compilación es incoherente y permanece incoherente hasta que ejecutes la herramienta de compilación de forma correcta. Describimos esta situación como una incoherencia inestable, ya que solo es temporal y se restablece la coherencia cuando se ejecuta la herramienta de compilación.
Existe otro tipo de inconsistencia perniciosa: la inconsistencia estable. Si la compilación alcanza un estado incoherente estable, la invocación correcta y repetida de la herramienta de compilación no restablece la coherencia: la compilación se “detiene” y los resultados siguen siendo incorrectos. Los estados estables incoherentes 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.
Conceptualmente, la forma más sencilla de lograr una compilación coherente es descartar todos los resultados de la compilación anterior y comenzar de nuevo: haz que cada compilación sea una compilación limpia. Es evidente que este enfoque lleva demasiado tiempo para ser práctico (excepto quizás para los ingenieros de lanzamiento) y, por lo tanto, para ser útil, la herramienta de compilación debe poder realizar compilaciones incrementales sin comprometer la coherencia.
El análisis correcto de la dependencia incremental es difícil y, como se describió anteriormente, muchas otras herramientas de compilación no hacen un buen trabajo al evitar estados inconsistentes estables durante las compilaciones incrementales. Por el contrario, Bazel ofrece la siguiente garantía: después de invocar correctamente la herramienta de compilación durante la cual no realizaste ediciones, la compilación tendrá 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, sí garantiza que los resultados de la compilación next restablecerán la coherencia).
Como con todas las garantías, hay una letra chica: hay algunas formas conocidas de llegar a un estado incoherente estable con Bazel. No garantizamos que investigaremos esos 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 inestables incoherentes 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 herméticamente y
correctamente. Bazel ejecuta creaciones (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, la zona de pruebas 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 la 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 del clúster de Google Kubernetes Engine o Debian, los espacios de nombres del usuario se desactivan de forma predeterminada debido a cuestiones de seguridad. Esto se puede verificar si observas el archivo /proc/sys/kernel/unprivileged_userns_clone
: si existe y contiene un 0, los espacios de nombres de usuario se pueden activar con sudo sysctl kernel.unprivileged_userns_clone=1
.
En algunos casos, la zona de pruebas de Bazel no puede ejecutar reglas debido a la configuración
del sistema. Por lo general, el síntoma es 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 la zona de pruebas para genrules con --strategy=Genrule=standalone
y para otras reglas con --spawn_strategy=standalone
. Además, informa un error en nuestra Herramienta de seguimiento de errores y menciona qué distribución de Linux usas para que podamos investigar y proporcionar una solució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
El primero es la carga, durante la cual se cargan, analizan, evalúan y almacenan en caché todos los archivos BUILD necesarios para los objetivos iniciales y su cierre transitivo de dependencias.
En la primera compilación después de iniciar un servidor de Bazel, la fase de carga suele tomar muchos segundos, ya que se cargan muchos archivos de COMPILACIÓN desde el sistema de archivos. En las compilaciones posteriores, en especial si no cambiaron archivos de BUILD, la carga se realiza con mucha rapidez.
Los errores informados durante esta fase incluyen los siguientes: paquete no encontrado, objetivo no encontrado, errores léxicos y gramaticales en un archivo de COMPILACIÓN, 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 dependencia de compilación y la determinación del trabajo exacto que se debe hacer en cada paso de la compilación.
Al igual que cuando se 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 hacer, lo que puede hacer que las compilaciones incrementales sean extremadamente rápidas en caso en que los paquetes no hayan cambiado desde la compilación anterior.
Los errores informados en esta etapa incluyen dependencias inapropiadas, entradas no válidas en una regla y todos los mensajes de error específicos de la regla.
Las fases de carga y análisis son rápidas, ya que Bazel evita las E/S de archivos innecesarias en esta etapa y lee solo los archivos de COMPILACIÓN para determinar el trabajo que se debe realizar. Esto es así por diseño y hace que Bazel sea una buena base para las herramientas de análisis, como el comando query de Bazel, que se implementa en 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 que se vuelvan a ejecutar las herramientas de compilación, vinculación, etc. 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, en el caso de una compilación grande. Entre los errores informados durante esta fase, se incluyen los siguientes: archivos fuente faltantes, errores en una herramienta que ejecuta alguna acción de compilación o la falla de una herramienta para producir el conjunto de resultados esperado.