Lenguaje de Starlark

En esta página, se brinda una descripción general de Starlark, antes conocido como Skylark, el lenguaje utilizado en Bazel. Para obtener una lista completa de las funciones y los tipos, consulta la referencia de la API de Bazel.

Para obtener más información sobre el lenguaje, consulta el repositorio de GitHub de Starlark.

Para obtener información sobre la especificación autorizada de la sintaxis y el comportamiento de Starlark, consulta la Especificación del lenguaje Starlark.

Sintaxis

La sintaxis de Starlark está inspirada en Python 3. Esta es una sintaxis válida en Starlark:

def fizz_buzz(n):
  """Print Fizz Buzz numbers from 1 to n."""
  for i in range(1, n + 1):
    s = ""
    if i % 3 == 0:
      s += "Fizz"
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)

fizz_buzz(20)

La semántica de Starlark puede diferir de Python, pero las diferencias de comportamiento son poco frecuentes, excepto en los casos en que Starlark genera un error. Se admiten los siguientes tipos de Python:

Mutabilidad

Starlark favorece la inmutabilidad. Hay dos estructuras de datos mutables disponibles: listas y dictos. Los cambios en las estructuras de datos mutables, como agregar un valor a una lista o borrar una entrada en un diccionario, son válidos solo para los objetos creados en el contexto actual. Cuando finaliza un contexto, sus valores se vuelven inmutables.

Esto se debe a que las compilaciones de Bazel usan ejecución paralela. Durante una compilación, cada archivo .bzl y cada archivo BUILD obtienen su propio contexto de ejecución. Cada regla también se analiza en su propio contexto.

Veamos un ejemplo con el archivo foo.bzl:

# `foo.bzl`
var = [] # declare a list

def fct(): # declare a function
  var.append(5) # append a value to the list

fct() # execute the fct function

Bazel crea var cuando se carga foo.bzl. Por lo tanto, var forma parte del contexto de foo.bzl. Cuando se ejecuta fct(), lo hace en el contexto de foo.bzl. Una vez que se completa la evaluación de foo.bzl, el entorno contiene una entrada inmutable, var, con el valor [5].

Cuando otro bar.bzl carga símbolos de foo.bzl, los valores cargados permanecen inmutables. Por este motivo, el siguiente código en bar.bzl es ilegal:

# `bar.bzl`
load(":foo.bzl", "var", "fct") # loads `var`, and `fct` from `./foo.bzl`

var.append(6)  # runtime error, the list stored in var is frozen

fct()          # runtime error, fct() attempts to modify a frozen list

No se pueden cambiar las variables globales definidas en los archivos bzl fuera del archivo bzl que las definió. Al igual que en el ejemplo anterior, que usa archivos bzl, los valores que muestran las reglas son inmutables.

Diferencias entre los archivos BUILD y .bzl

Los archivos BUILD registran objetivos mediante llamadas a las reglas. Los archivos .bzl proporcionan definiciones de constantes, reglas, macros y funciones.

Las funciones nativas y las reglas nativas son símbolos globales en archivos BUILD. Los archivos bzl deben cargarlos con el módulo native.

Hay dos restricciones sintácticas en los archivos BUILD: 1) la declaración de funciones es ilegal, y 2) los argumentos *args y **kwargs no están permitidos.

Diferencias con Python

  • Las variables globales son inmutables.

  • Las declaraciones for no están permitidas en el nivel superior. En su lugar, úsalos dentro de otras funciones. En los archivos BUILD, puedes usar comprensión de listas.

  • Las declaraciones if no están permitidas en el nivel superior. Sin embargo, las expresiones if se pueden usar: first = data[0] if len(data) > 0 else None.

  • Orden determinista para iterar a través de diccionarios

  • No se permite la recurrencia.

  • El tipo int está limitado a números enteros de 32 bits con signo. Los desbordamientos arrojarán un error.

  • Modificar una colección durante la iteración es un error.

  • A excepción de las pruebas de igualdad, los operadores de comparación <, <=, >=, >, etc. no se definen en todos los tipos de valores. En resumen, 5 < 'foo' arrojará un error y 5 == "5" mostrará falso.

  • En las tuplas, una coma final solo es válida cuando la tupla está entre paréntesis: cuando escribes (1,) en lugar de 1,.

  • Los literales de diccionario no pueden tener claves duplicadas. Por ejemplo, este es un error: {"a": 4, "b": 7, "a": 1}.

  • Las strings se representan con comillas dobles (por ejemplo, cuando llamas a repr).

  • Las strings no son iterables.

Las siguientes funciones de Python no son compatibles:

  • concatenación de string implícita (usa el operador + explícito).
  • Comparaciones encadenadas (como 1 < x < 5).
  • class (consulta la función struct).
  • import (consulta la declaración load).
  • while, yield.
  • float y configura los tipos.
  • generadores y expresiones de generadores.
  • is (en su lugar, usa ==).
  • try, raise, except, finally (consulta fail para ver los errores fatales).
  • global, nonlocal.
  • la mayoría de las funciones integradas y métodos.