Guía de estilo .bzl

En esta página, se describen los lineamientos de estilo básicos de Starlark y también se incluye información sobre macros y reglas.

Starlark es un lenguaje que define cómo se compila el software y, por lo tanto, es un lenguaje de programación y de configuración.

Usarás Starlark para escribir archivos BUILD, macros y reglas de compilación. Las macros y las reglas son, en esencia, metalenguajes; definen cómo se escriben los archivos BUILD. Los archivos BUILD están diseñados para ser simples y repetitivos.

Todo el software se lee con más frecuencia de la que se escribe. Esto es particularmente cierto para Starlark, ya que los ingenieros leen archivos BUILD para comprender las dependencias de sus destinos y los detalles de sus compilaciones. Esta lectura suele ocurrir de manera anticipada, apurada o en paralelo a la realización de alguna otra tarea. En consecuencia, la simplicidad y la legibilidad son muy importantes para que los usuarios puedan analizar y comprender los archivos BUILD rápidamente.

Cuando un usuario abre un archivo BUILD, quiere conocer rápidamente la lista de destinos del archivo, revisar la lista de fuentes de esa biblioteca de C++ o quitar una dependencia de ese objeto binario de Java. Cada vez que agregas una capa de abstracción, dificultas que un usuario realice estas tareas.

Los archivos BUILD también se analizan y actualizan con muchas herramientas diferentes. Es posible que las herramientas no puedan editar tu archivo BUILD si usa abstracciones. Mantener los archivos BUILD simples te permitirá obtener mejores herramientas. A medida que crece una base de código, se vuelve cada vez más frecuente realizar cambios en muchos archivos BUILD para actualizar una biblioteca o realizar una limpieza.

Consejo general

Estilo

Estilo Python

Si tienes dudas, sigue la guía de estilo de PEP 8 siempre que sea posible. En particular, usa cuatro en lugar de dos espacios para que la sangría siga la convención de Python.

Dado que Starlark no es Python, algunos aspectos del estilo de Python no se aplican. Por ejemplo, PEP 8 recomienda que las comparaciones con singletons se realicen con is, que no es un operador en Starlark.

Cadena de documentos

Documenta archivos y funciones con docstrings. Usa una docstring en la parte superior de cada archivo .bzl y una docstring para cada función pública.

Reglas y aspectos del documento

Las reglas y los aspectos, junto con sus atributos, así como los proveedores y sus campos, deben documentarse mediante el argumento doc.

Convención de nombres

  • Las variables y los nombres de las funciones usan minúsculas con palabras separadas por guiones bajos ([a-z][a-z0-9_]*), como cc_library.
  • Los valores privados de nivel superior comienzan con un guion bajo. Bazel exige que los valores privados no se puedan usar desde otros archivos. Las variables locales no deben usar el prefijo de guion bajo.

Longitudes de línea

Al igual que en los archivos BUILD, no hay un límite estricto de longitud de línea, ya que las etiquetas pueden ser largas. Cuando sea posible, intenta usar un máximo de 79 caracteres por línea (siguiendo la guía de estilo de Python, PEP 8). Este lineamiento no se debe aplicar de forma estricta: los editores deben mostrar más de 80 columnas, los cambios automatizados suelen introducir líneas más largas y las personas no deben perder tiempo dividiendo las líneas que ya son legibles.

Argumentos de palabras clave

En los argumentos de palabras clave, se prefieren los espacios alrededor del signo igual:

def fct(name, srcs):
    filtered_srcs = my_filter(source = srcs)
    native.cc_library(
        name = name,
        srcs = filtered_srcs,
        testonly = True,
    )

Valores booleanos

Elige los valores True y False (en lugar de 1 y 0) para los valores booleanos (por ejemplo, cuando se usa un atributo booleano en una regla).

No uses la función print() en código de producción. Solo está diseñada para la depuración y enviará spam a todos los usuarios indirectos y directos del archivo .bzl. La única excepción es que puedes enviar un código que use print() si está inhabilitado de forma predeterminada y solo se puede habilitar mediante la edición de la fuente, por ejemplo, si todos los usos de print() están protegidos por if DEBUG:, en el que DEBUG se codifica en False. Ten en cuenta si estas declaraciones son lo suficientemente útiles como para justificar su impacto en la legibilidad.

Macros

Una macro es una función que crea una instancia de una o más reglas durante la fase de carga. En general, siempre que sea posible, usa reglas en lugar de macros. El gráfico de compilación que ve el usuario no es el mismo que usa Bazel durante la compilación: las macros se expanden antes de que Bazel realice cualquier análisis del gráfico de compilación.

Por lo tanto, cuando algo salga mal, el usuario deberá comprender la implementación de la macro para solucionar problemas de compilación. Además, los resultados de bazel query pueden ser difíciles de interpretar porque los objetivos que se muestran en los resultados provienen de la expansión macro. Por último, los aspectos no reconocen las macros, por lo que las herramientas en función de ellos (IDEs y otros) podrían fallar.

Un uso seguro de las macros es definir destinos adicionales destinados a los que se haga referencia directamente en la CLI de Bazel o en los archivos de COMPILACIÓN: En ese caso, solo los usuarios finales de esos destinos deben conocerlos, y los problemas de compilación que presentan las macros nunca están lejos de su uso.

Para las macros que definen objetivos generados (detalles de implementación de la macro a los que no se hace referencia en la CLI o que no depende de objetivos que no crearon instancias de esa macro), sigue estas prácticas recomendadas:

  • Una macro debe tener un argumento name y definir un objetivo con ese nombre. Ese objetivo se convierte en el objetivo principal de la macro.
  • Los objetivos generados, es decir, todos los demás destinos definidos por una macro, deben cumplir con los siguientes requisitos:
    • Sus nombres tienen el prefijo <name> o _<name>. Por ejemplo, mediante name = '%s_bar' % (name).
    • Tienen visibilidad restringida (//visibility:private).
    • Incluye una etiqueta manual para evitar la expansión en destinos con comodines (:all, ..., :*, etcétera).
  • El name solo se debe usar para derivar nombres de objetivos definidos por la macro y no para ningún otro fin. Por ejemplo, no uses el nombre para derivar una dependencia o un archivo de entrada que la macro en sí no haya generado.
  • Todos los objetivos creados en la macro deben vincularse de alguna manera con el objetivo principal.
  • Mantén la coherencia de los nombres de los parámetros en la macro. Si se pasa un parámetro como un valor de atributo al objetivo principal, mantén el mismo nombre. Si un parámetro de macro tiene el mismo propósito que un atributo de regla común, como deps, nombre que el que tendrías que usar el atributo (consulta a continuación).
  • Cuando llames a una macro, usa solo argumentos de palabras clave. Esto es coherente con las reglas y mejora considerablemente la legibilidad.

Los ingenieros suelen escribir macros cuando la API de Starlark de reglas relevantes no es suficiente para su caso de uso específico, sin importar si la regla se define dentro de Bazel en código nativo o en Starlark. Si tienes este problema, pregúntale al autor de la regla si puede extender la API para lograr tus objetivos.

Como regla general, cuanto más macros se parezcan a las reglas, mejor.

Consulta también macros.

Reglas

  • Las reglas, los aspectos y sus atributos deben usar nombres en minúscula ("snake case").
  • Los nombres de las reglas son sustantivos que describen el tipo principal de artefacto que produce la regla, desde el punto de vista de sus dependencias (o para las reglas de hoja, el usuario). Esto no es necesariamente un sufijo de archivo. Por ejemplo, una regla que genera artefactos de C++ diseñados para usarse como extensiones de Python podría llamarse py_extension. Para la mayoría de los idiomas, las reglas típicas incluyen las siguientes:
    • *_library: Es una unidad de compilación o "módulo".
    • *_binary: Es un destino que produce un ejecutable o una unidad de implementación.
    • *_test: Es un objetivo de prueba. Esto puede incluir varias pruebas. Se espera que todas las pruebas en un destino *_test sean variaciones del mismo tema, por ejemplo, probar una sola biblioteca.
    • *_import: Es un destino que encapsula un artefacto precompilado, como .jar o .dll que se usa durante la compilación.
  • Usa nombres y tipos coherentes para los atributos. Estos son algunos atributos que generalmente se aplican:
    • srcs: label_list, que permite archivos: de origen, generalmente creados por personas.
    • deps: label_list; por lo general, no permite archivos: dependencias de compilación.
    • data: label_list, lo que permite archivos (archivos de datos, como datos de prueba, etcétera)
    • runtime_deps: label_list: Son dependencias de entorno de ejecución que no se necesitan para la compilación.
  • En el caso de cualquier atributo con comportamiento no evidente (por ejemplo, plantillas de cadenas con sustituciones especiales o herramientas que se invoquen con requisitos específicos), proporciona documentación con el argumento de palabra clave doc a la declaración del atributo (attr.label_list() o similar).
  • Las funciones de implementación de reglas casi siempre deben ser funciones privadas (nombradas con un guion bajo inicial). Un diseño común es asignar el nombre _myrule_impl a la función de implementación de myrule.
  • Pasa información entre tus reglas con una interfaz provider bien definida. Declara y documenta los campos del proveedor.
  • Ten en cuenta la extensibilidad al diseñar la regla. Considera que otras reglas podrían querer interactuar con tu regla, acceder a tus proveedores y reutilizar las acciones que crees.
  • Sigue los lineamientos de rendimiento en tus reglas.