Cómo usar macros para crear verbos personalizados

Informar un problema Ver fuente Nightly · 8.0 . 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

La interacción diaria con Bazel se realiza principalmente a través de algunos comandos: build, test y run. Sin embargo, a veces, pueden parecer limitados: es posible que desees enviar paquetes a un repositorio, publicar documentación para los usuarios finales o implementar una aplicación con Kubernetes. Sin embargo, Bazel no tiene un comando publish ni 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 deseas 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 producción: rules_k8s

Por ejemplo, considera rules_k8s, las reglas de Kubernetes para Bazel. Supongamos que tienes el siguiente objetivo:

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

La regla k8s_object compila un archivo YAML de Kubernetes estándar 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 compilan secuencias de comandos para realizar 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 objetivo nodejs_test estándar que compara algunos resultados generados con un archivo “de referencia” (es decir, un archivo que contiene el resultado esperado). Se puede compilar y ejecutar con una invocación normal de bazel test. En angular-cli, puedes ejecutar uno de esos objetivos con bazel test //etc/api:angular_devkit_core_api.

Con el tiempo, es posible que debas actualizar este archivo de referencia por motivos legítimos. Actualizar esto de forma manual es tedioso y propenso a errores, por lo que esta macro también proporciona un objetivo nodejs_binary que actualiza el archivo de referencia, en lugar de compararlo. En efecto, se puede escribir la misma secuencia de comandos de prueba para que se ejecute en el modo "verify" o "accept", 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 bastante potente y resulta 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 objetivo con el nombre especificado que realice la acción de compilación principal: quizás compile un objeto binario normal, una imagen de Docker o un archivo de código fuente. En este patrón, se crean objetivos adicionales para producir secuencias de comandos que realizan efectos secundarios en función del resultado del objetivo principal, como publicar el objeto binario resultante o actualizar el resultado de la prueba esperado.

Para ilustrar esto, une una regla imaginaria que genere un sitio web con Sphinx con una macro para crear un objetivo adicional que le permita al usuario publicarlo cuando esté listo. Ten en cuenta 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 para crear objetivos para ambas reglas anteriores:

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 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 objetivo adicional "docs.publish", que compila una secuencia de comandos para publicar el sitio. Una vez que verifiques el resultado del objetivo principal, puedes usar bazel run :docs.publish para publicarlo para el consumo público, como un comando bazel publish imaginario.

No es evidente de inmediato cómo se vería la implementación de la regla _sphinx_publisher. A menudo, acciones como esta escriben una secuencia de comandos de shell de iniciador. Por lo general, este método implica usar ctx.actions.expand_template para escribir una secuencia de comandos de shell muy simple, en este caso, invocar el objeto binario del publicador con una ruta de acceso al resultado del objetivo 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 ambas.

En rules_k8s, esto es lo que hace .apply: expand_template escribe una secuencia de comandos de Bash muy simple, basada en apply.sh.tpl, que ejecuta kubectl con el resultado del objetivo 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.