En esta página, se incluyen los lineamientos de estilo básicos para Starlark y también información sobre macros y reglas.
Starlark es un lenguaje que define cómo se compila el software y, como tal, 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
.
El objetivo de los archivos BUILD
es que sean simples y repetitivos.
Todo el software se lee con más frecuencia de la que se escribe. Esto es especialmente cierto para
Starlark, ya que los ingenieros leen archivos BUILD
para comprender las dependencias de sus
objetivos y los detalles de sus compilaciones. Esta lectura suele ocurrir de pasada,
en un apuro o en paralelo con la realización de alguna otra tarea. Por lo tanto, la simplicidad y la legibilidad son muy importantes para que los usuarios puedan analizar y comprender los archivos BUILD
con rapidez.
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 archivo binario de Java. Cada vez que agregas una capa de abstracción, le dificultas al usuario realizar 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 la simplicidad de los archivos BUILD
te permitirá obtener mejores herramientas. A medida que crece una base de código, es cada vez más frecuente realizar cambios en muchos archivos BUILD
para actualizar una biblioteca o realizar una limpieza.
Consejo general
- Usa Buildifier como formateador y lint.
- Sigue los lineamientos de pruebas.
Estilo
Estilo de Python
Si tienes dudas, sigue la guía de estilo PEP 8 siempre que sea posible. En particular, usa cuatro espacios en lugar de dos para que la sangría siga la convención de Python.
Dado que Starlark no es Python, no se aplican algunos aspectos del estilo de Python. Por ejemplo, PEP 8 recomienda que las comparaciones con objetos singleton 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.
Documenta las reglas y los aspectos
Las reglas y los aspectos, junto con sus atributos, así como los proveedores y sus
campos, se deben documentar con el argumento doc
.
Convención de nombres
- Los nombres de las variables y funciones usan minúsculas con palabras separadas por guiones bajos (
[a-z][a-z0-9_]*
), comocc_library
. - Los valores privados de nivel superior comienzan con un guion bajo. Bazel aplica que los valores privados no se pueden usar desde otros archivos. Las variables locales no deben usar el prefijo de guion bajo.
Longitud de la 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 79 caracteres como máximo por línea (según la guía de estilo de Python, PEP 8). Este lineamiento no debe aplicarse estrictamente: los editores deben mostrar más de 80 columnas, los cambios automatizados suelen introducir líneas más largas y las personas no deberían dedicar tiempo a dividir 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
Da preferencia a los valores True
y False
(en lugar de 1
y 0
) para valores booleanos (como cuando se usa un atributo booleano en una regla).
Usa la impresión solo para depuración
No uses la función print()
en el código de producción, ya que 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 código que use print()
si está inhabilitado de forma predeterminada y solo se puede habilitar editando la fuente; por ejemplo, si todos los usos de print()
están protegidos por if DEBUG:
, donde DEBUG
está codificado en False
. Ten en cuenta si estas afirmaciones 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, usa reglas siempre que sea posible 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, ya que las macros se expanden antes de que Bazel realice cualquier análisis del gráfico de compilación.
Por este motivo, cuando algo salga mal, el usuario deberá comprender
la implementación de tu 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 de macros. Por último, los aspectos no conocen las macros, por lo que las herramientas que dependen de los aspectos (IDEs y otros) pueden fallar.
Un uso seguro de las macros es definir objetivos adicionales a los que se hace referencia directamente en la CLI de Bazel o en los archivos BUILD. En ese caso, solo los usuarios finales de esos objetivos deben conocerlos, y cualquier problema de compilación que introduzcan las macros nunca está lejos de su uso.
En el caso de las macros que definen objetivos generados (detalles de implementación de la macro a los que no se debe hacer referencia en la CLI o de los que dependen los objetivos que no crean instancias esa macro), sigue estas prácticas recomendadas:
- Una macro debe tomar un argumento
name
y definir un objetivo con ese nombre. Ese objetivo se convierte en el objetivo principal de esa macro. - Los objetivos generados, es decir, todos los demás objetivos definidos por una macro, deben cumplir con los siguientes requisitos:
- Tienen el prefijo
<name>
o_<name>
. Por ejemplo, usaname = '%s_bar' % (name)
. - Tener visibilidad restringida (
//visibility:private
) - Tener una etiqueta
manual
para evitar la expansión en los objetivos de comodines (:all
,...
,:*
, etcétera)
- Tienen el prefijo
- El
name
solo debe usarse para derivar nombres de destinos definidos por la macro, no para nada más. Por ejemplo, no uses el nombre para derivar una dependencia o un archivo de entrada que no genere la macro. - Todos los destinos creados en la macro deben acoplarse de alguna manera al objetivo principal.
- De manera convencional,
name
debe ser el primer argumento cuando se define una macro. - Mantén la coherencia de los nombres de los parámetros en la macro. Si se pasa un parámetro como valor de atributo al objetivo principal, mantén el mismo nombre. Si el parámetro de una macro tiene el mismo propósito que un atributo de regla común, como
deps
, asigna un nombre al atributo (consulta a continuación). - Cuando llames a una macro, usa solo argumentos de palabras clave. Esto es coherente con las reglas y mejora mucho la legibilidad.
Los ingenieros suelen escribir macros cuando la API de Starlark de las reglas relevantes es insuficiente para su caso de uso específico, independientemente de 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, cuantas 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úsculas ("minúsculas").
- 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). No es necesariamente un sufijo de archivo. Por ejemplo, una regla que produce artefactos de C++ destinados a usarse como extensiones de Python podría llamarse
py_extension
. Para la mayoría de los lenguajes, las reglas típicas incluyen las siguientes:*_library
: Es una unidad o "módulo" de compilación.*_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 objetivo*_test
sean variaciones del mismo tema, por ejemplo, probar una sola biblioteca.*_import
: Es un destino que encapsula un artefacto precompilado, como un.jar
o un.dll
que se usa durante la compilación.
- Usa nombres y tipos coherentes para los atributos. Estos son algunos atributos aplicables en general:
srcs
:label_list
, que permite archivos: archivos de origen, por lo general, creados por humanos.deps
:label_list
, por lo general, no permite archivos: dependencias de compilación.data
:label_list
, que permite archivos: archivos de datos, como datos de prueba, etcétera.runtime_deps
:label_list
: Son dependencias del entorno de ejecución que no se necesitan para la compilación.
- Para los atributos con un comportamiento no obvio (por ejemplo, plantillas de cadenas con sustituciones especiales o herramientas que se invocan con requisitos específicos), proporciona documentación con el argumento de palabra clave
doc
en la declaración del atributo (attr.label_list()
o similar). - Las funciones de implementación de reglas casi siempre deben ser funciones privadas (con un guion bajo al principio). Un estilo común es asignarle el nombre
_myrule_impl
a la función de implementación demyrule
. - Pasa información entre tus reglas con una interfaz de proveedor bien definida. Declara y documenta los campos del proveedor.
- Diseña tu regla teniendo en cuenta la extensibilidad. Ten en cuenta que es posible que otras reglas quieran interactuar con tu regla, acceder a tus proveedores y volver a usar las acciones que crees.
- Sigue los lineamientos de rendimiento en tus reglas.