La interacción diaria con Bazel se produce principalmente a través de algunos comandos: build
, test
y run
. Sin embargo, a veces, pueden parecer limitadas: es posible que desees enviar paquetes a un repositorio, publicar documentación para los usuarios finales o implementar una aplicación con Kubernetes. Pero Bazel no tiene un comando publish
o deploy
. ¿Dónde encajan estas acciones?
El comando bazel run
El enfoque de Bazel en la hermeticidad, la reproducibilidad y la incrementalidad significa que los comandos build
y test
no son útiles para las tareas anteriores. Estas acciones pueden ejecutarse en una zona de pruebas, con acceso limitado a la red, y no se garantiza que se vuelvan a ejecutar con cada bazel build
.
En su lugar, usa bazel run
, el caballo de batalla para las tareas que quieres que tengan efectos secundarios. Los usuarios de Bazel están acostumbrados a las reglas que crean ejecutables, y los autores de reglas pueden seguir un conjunto común de patrones para extender esto a "verbos personalizados".
En la naturaleza: rules_k8s
Por ejemplo, considera rules_k8s
, las reglas de Kubernetes para Bazel. Supongamos que tienes el siguiente destino:
# BUILD file in //application/k8s
k8s_object(
name = "staging",
kind = "deployment",
cluster = "testing",
template = "deployment.yaml",
)
La regla k8s_object
compila un archivo YAML estándar de Kubernetes cuando se usa bazel build
en el destino staging
. Sin embargo, la macro k8s_object
también crea los destinos adicionales con nombres como staging.apply
y :staging.delete
. Estas secuencias de comandos de compilación realizan esas acciones y, cuando se ejecutan con bazel run
staging.apply
, se comportan como nuestros propios comandos bazel k8s-apply
o bazel
k8s-delete
.
Otro ejemplo: ts_api_guardian_test
Este patrón también se puede ver en el proyecto de Angular. La macro ts_api_guardian_test
produce dos destinos. El primero es un destino nodejs_test
estándar que compara algún resultado generado con un archivo "dorado" (es decir, un archivo que contiene el resultado esperado). Esto se puede compilar y ejecutar con una invocación normal de bazel
test
. En angular-cli
, puedes ejecutar uno de esos destinos con bazel test //etc/api:angular_devkit_core_api
.
Con el tiempo, es posible que este archivo dorado deba actualizarse por motivos legítimos.
Actualizar esto de forma manual es tedioso y propenso a errores, por lo que esta macro también proporciona un destino nodejs_binary
que actualiza el archivo de referencia, en lugar de compararlo. En efecto, la misma secuencia de comandos de prueba se puede escribir para que se ejecute en el modo "verificar" o "aceptar", según cómo se invoque. Esto sigue el mismo patrón que ya aprendiste: no hay un comando bazel test-accept
nativo, pero se puede lograr el mismo efecto con bazel run //etc/api:angular_devkit_core_api.accept
.
Este patrón puede ser muy eficaz y, de hecho, es bastante común una vez que aprendes a reconocerlo.
Cómo adaptar tus propias reglas
Las macros son el corazón de este patrón. Las macros se usan como reglas, pero pueden crear varios objetivos. Por lo general, crearán un destino con el nombre especificado que realiza la acción de compilación principal: tal vez compile un archivo binario normal, una imagen de Docker o un archivo de código fuente. En este patrón, se crean destinos adicionales para producir secuencias de comandos que realizan efectos secundarios basados en el resultado del destino principal, como publicar el archivo binario resultante o actualizar el resultado de prueba esperado.
Para ilustrar esto, incluye una regla imaginaria que genera un sitio web con Sphinx con una macro para crear un destino adicional que permita al usuario publicarlo cuando esté listo. Considera la siguiente regla existente para generar un sitio web con Sphinx:
_sphinx_site = rule(
implementation = _sphinx_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)
A continuación, considera una regla como la siguiente, que compila una secuencia de comandos que, cuando se ejecuta, publica las páginas generadas:
_sphinx_publisher = rule(
implementation = _publish_impl,
attrs = {
"site": attr.label(),
"_publisher": attr.label(
default = "//internal/sphinx:publisher",
executable = True,
),
},
executable = True,
)
Por último, define la siguiente macro simbólica (disponible en Bazel 8 o versiones posteriores) para crear destinos para ambas reglas anteriores:
def _sphinx_site_impl(name, visibility, srcs, **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML. We
# set `visibility = visibility` to make it visible to callers of the
# macro.
_sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above. We don't want it to be visible to callers of
# our macro, so we omit visibility for it.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
sphinx_site = macro(
implementation = _sphinx_site_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
# Inherit common attributes like tags and testonly
inherit_attrs = "common",
)
O bien, si necesitas admitir versiones de Bazel anteriores a la 8, define una macro heredada:
def sphinx_site(name, srcs = [], **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML.
_sphinx_site(name = name, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
En los archivos BUILD
, usa la macro como si solo creara el objetivo principal:
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
En este ejemplo, se crea un destino "docs", como si la macro fuera una regla única y estándar de Bazel. Cuando se compila, la regla genera cierta configuración y ejecuta Sphinx para producir un sitio HTML, listo para la inspección manual. Sin embargo, también se crea un destino "docs.publish" adicional, que compila un script para publicar el sitio. Una vez que verifiques el resultado del destino principal, puedes usar bazel run :docs.publish
para publicarlo para el consumo público, al igual que un comando bazel publish
imaginario.
No es evidente de inmediato cómo podría ser la implementación de la regla _sphinx_publisher
. A menudo, las acciones como esta escriben una secuencia de comandos de shell del launcher.
Por lo general, este método implica usar ctx.actions.expand_template
para escribir una secuencia de comandos muy simple, que, en este caso, invoca el ejecutable del publicador con una ruta de acceso al resultado del destino principal. De esta manera, la implementación del publicador puede seguir siendo genérica, la regla _sphinx_site
solo puede producir HTML y esta pequeña secuencia de comandos es todo lo que se necesita para combinar ambos elementos.
En rules_k8s
, esto es lo que hace .apply
:
expand_template
escribe una secuencia de comandos Bash muy simple, basada en apply.sh.tpl
, que ejecuta kubectl
con el resultado del destino principal. Luego, esta secuencia de comandos se puede compilar y ejecutar con bazel run :staging.apply
, lo que proporciona de manera efectiva un comando k8s-apply
para los destinos k8s_object
.