Starlark es una herramienta similar a Python.
de configuración de lenguaje natural, originalmente desarrollado para su uso en Bazel y desde su adopción
por otras herramientas. Los archivos BUILD
y .bzl
de Bazel están escritos en un dialecto de
Starlark, bien conocido como el "lenguaje de compilación", aunque a menudo se trata de
se conoce como "Starlark", especialmente cuando enfatiza que un atributo es
expresada en el lenguaje de compilación, en lugar de ser una herramienta integrada o "nativa" pieza
de Bazel. Bazel aumenta el lenguaje principal con numerosas funciones relacionadas con la compilación
como glob
, genrule
, java_binary
, etcétera.
Consulta la Documentación de Bazel y Starlark para más detalles, y Plantilla de reglas de SIG como un como punto de partida para los nuevos conjuntos de reglas.
La regla vacía
Para crear tu primera regla, crea el archivo foo.bzl
:
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
Cuando llamas a la función rule
,
debes definir una función de devolución de llamada. La lógica irá allí, pero
puedes dejar la función vacía por ahora. El argumento ctx
proporciona información sobre el objetivo.
Puedes cargar la regla y usarla desde un archivo BUILD
.
Crea un archivo BUILD
en el mismo directorio:
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
Ahora, se puede crear el destino:
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
Si bien la regla no hace nada, ya se comporta como otras reglas: tiene un
obligatorio, admite atributos comunes como visibility
, testonly
y
tags
Modelo de evaluación
Antes de continuar, es importante comprender cómo se evalúa el código.
Actualiza foo.bzl
con algunas sentencias de impresión:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
y CREA:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
corresponde a la etiqueta del objetivo que se está analizando. El objeto ctx
tiene
muchos campos y métodos útiles. puedes encontrar una lista exhaustiva en
Referencia de la API.
Consulta el código:
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
Haz algunas observaciones:
- “evaluación de archivo bzl” se imprime primero. Antes de evaluar el archivo
BUILD
, Bazel evalúa todos los archivos que carga. Si se están cargando varios archivosBUILD
foo.bzl, verías solo un caso de “evaluación del archivo bzl” porque Bazel almacena en caché el resultado de la evaluación. - No se llama a la función de devolución de llamada
_foo_binary_impl
. Se carga la consulta de BazelBUILD
, pero no analiza objetivos.
Para analizar los objetivos, usa la cquery
("configurada
consulta") o el comando build
:
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
Como puedes ver, ahora se llama a _foo_binary_impl
dos veces, una para cada destino.
Ten en cuenta que ni la “evaluación del archivo bzl” ni "archivo BUILD" se vuelven a imprimir
porque la evaluación de foo.bzl
se almacena en caché después de la llamada a bazel query
.
Bazel solo emite sentencias print
cuando se ejecutan realmente.
Cómo crear un archivo
Para que tu regla sea más útil, actualízala para generar un archivo. Primero, declara el y asígnale un nombre. En este ejemplo, crea un archivo con el mismo nombre que el objetivo:
ctx.actions.declare_file(ctx.label.name)
Si ejecutas bazel build :all
ahora, verás un error:
The following files have no generating action:
bin2
Cada vez que declaras un archivo, debes indicarle a Bazel cómo generarlo.
crear una acción. Usa ctx.actions.write
,
para crear un archivo con el contenido dado.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
El código es válido, pero no hará nada:
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
La función ctx.actions.write
registró una acción, que enseñó a Bazel
cómo generar el archivo. Pero Bazel no creará el archivo hasta que
solicitado realmente. Por último, lo último que hay que hacer es decirle a Bazel que el archivo
Es un resultado de la regla y no un archivo temporal que se usa dentro de ella.
para implementarlos.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
Observa las funciones DefaultInfo
y depset
más adelante. Por ahora,
supón que la última línea es la forma de elegir los resultados de una regla.
Ahora, ejecuta Bazel:
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
Generaste un archivo correctamente.
Atributos
Para que la regla sea más útil, agrega atributos nuevos con
el módulo attr
y actualiza la definición de la regla.
Agrega un atributo de cadena llamado username
:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
Luego, configúrala en el archivo BUILD
:
foo_binary(
name = "bin",
username = "Alice",
)
Para acceder al valor de la función de devolución de llamada, usa ctx.attr.username
. Por ejemplo:
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
Ten en cuenta que puedes hacer que el atributo sea obligatorio o establecer un valor predeterminado. Mira
la documentación de attr.string
.
También puede usar otros tipos de atributos, como los booleanos
o una lista de números enteros.
Dependencias
Atributos de dependencia, como attr.label
y attr.label_list
,
declarar una dependencia del objetivo que posee el atributo al objetivo cuyo
la etiqueta aparece en el valor del atributo. Este tipo de atributo conforma la base
del gráfico de destino.
En el archivo BUILD
, la etiqueta de destino aparece como un objeto de cadena, como
//pkg:name
En la función de implementación, se podrá acceder al destino como
Objeto Target
. Por ejemplo, visualiza los archivos que se muestran
por el destino con Target.files
.
Varios archivos
De forma predeterminada, solo los objetivos creados por reglas pueden aparecer como dependencias (por ejemplo, un
foo_library()
). Si deseas que el atributo acepte destinos que tengan las siguientes características
archivos de entrada (como los archivos fuente en el repositorio), puedes hacerlo con
allow_files
y especifica la lista de extensiones de archivo aceptadas (o True
para
Permitir cualquier extensión de archivo):
"srcs": attr.label_list(allow_files = [".java"]),
Se puede acceder a la lista de archivos con ctx.files.<attribute name>
. Para
Por ejemplo, puedes acceder a la lista de archivos del atributo srcs
con
ctx.files.srcs
Archivo único
Si solo necesitas un archivo, usa allow_single_file
:
"src": attr.label(allow_single_file = [".java"])
Luego, se puede acceder a este archivo en ctx.file.<attribute name>
:
ctx.file.src
Crea un archivo con una plantilla
Puedes crear una regla que genere un archivo .cc basado en una plantilla. Además,
puedes usar ctx.actions.write
para mostrar una cadena construida en la regla
función de implementación, pero esta tiene dos problemas. Primero, a medida que la plantilla
más grande, se vuelve más eficiente en cuanto a memoria colocarlo en un archivo separado y evitar
construir cadenas grandes durante la fase de análisis. En segundo lugar, el uso de un
sea más conveniente para el usuario. En cambio, usa
ctx.actions.expand_template
:
que realiza sustituciones en un archivo de plantilla.
Crea un atributo template
para declarar una dependencia en la plantilla.
archivo:
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
Los usuarios pueden usar la regla de la siguiente manera:
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
Si no quieres exponer la plantilla al usuario final y siempre debes usar mismo, puedes establecer un valor predeterminado y hacer privado el atributo:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
Los atributos que comienzan con un guion bajo son privados y no se pueden configurar en un
BUILD
. La plantilla ahora es una dependencia implícita: Cada hello_world
el destino tiene una dependencia en este archivo. No olvides hacer visible este archivo
a otros paquetes actualizando el archivo BUILD
y usando
exports_files
:
exports_files(["file.cc.tpl"])
Un paso más allá
- Consulta la documentación de referencia sobre las reglas.
- Familiarízate con las opciones.
- Consulta el repositorio de ejemplos que incluye ejemplos adicionales de reglas.