O Starlark é uma linguagem de configuração
semelhante ao Python originalmente desenvolvida para uso no Bazel e adotada
por outras ferramentas. Os arquivos BUILD
e .bzl
do Bazel são escritos em um dialeto do
Starlark conhecido como "Build Language", embora muitas vezes seja simplesmente
chamado de "Starlark", especialmente ao enfatizar que um recurso é
expresso na linguagem de build, em vez de ser uma parte integrada ou "nativa"
do Bazel. O Bazel aumenta a linguagem principal com várias funções relacionadas ao build, como glob
, genrule
, java_binary
e assim por diante.
Consulte a documentação do Bazel e do Starlark para mais detalhes, e o modelo de SIG de regras como ponto de partida para novos conjuntos de regras.
Regra vazia
Para criar a primeira regra, crie o arquivo foo.bzl
:
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
Ao chamar a função rule
, você
precisa definir uma função de callback. A lógica funcionará, mas você
pode deixar a função vazia por enquanto. O argumento ctx
fornece informações sobre o destino.
É possível carregar a regra e usá-la em um arquivo BUILD
.
Crie um arquivo BUILD
no mesmo diretório:
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
Agora, o destino pode ser criado:
$ 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)
Embora a regra não faça nada, ela já se comporta como outras: ela tem um
nome obrigatório e é compatível com atributos comuns, como visibility
, testonly
e
tags
.
Modelo de avaliação
Antes de continuar, é importante entender como o código é avaliado.
Atualize foo.bzl
com algumas instruções de impressão:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
e CRIAÇÃO:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
corresponde ao rótulo do alvo que está sendo analisado. O objeto ctx
tem
muitos campos e métodos úteis. Encontre uma lista completa na
referência da API.
Consultar o 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
Faça algumas observações:
- A mensagem "bzl file evaluation" é mostrada primeiro. Antes de avaliar o arquivo
BUILD
, o Bazel avalia todos os arquivos que ele carrega. Se vários arquivosBUILD
estiverem carregando foo.bzl, você verá apenas uma ocorrência de "avaliação de arquivo bzl" porque o Bazel armazena em cache o resultado da avaliação. - A função de callback
_foo_binary_impl
não é chamada. A consulta do Bazel carrega arquivosBUILD
, mas não analisa destinos.
Para analisar os destinos, use o cquery
("consulta
configurada") ou o 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 você pode ver, _foo_binary_impl
agora é chamado duas vezes, uma para cada destino.
Observe que nem "avaliação do arquivo bzl" nem "arquivo BUILD" são mostrados novamente,
porque a avaliação de foo.bzl
é armazenada em cache após a chamada para bazel query
.
O Bazel só emite instruções print
quando elas são realmente executadas.
Como criar um arquivo
Para tornar a regra mais útil, atualize-a para gerar um arquivo. Primeiro, declare o arquivo e dê um nome a ele. Neste exemplo, crie um arquivo com o mesmo nome do destino:
ctx.actions.declare_file(ctx.label.name)
Se você executar bazel build :all
agora, vai receber um erro:
The following files have no generating action:
bin2
Sempre que você declarar um arquivo, vai precisar informar ao Bazel como gerá-lo
criando uma ação. Use ctx.actions.write
para criar um arquivo com o conteúdo especificado.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
O código é válido, mas não faz nada:
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
A função ctx.actions.write
registrou uma ação, que ensinou o Bazel
a gerar o arquivo. Mas o Bazel não vai criar o arquivo até que ele seja
realmente solicitado. A última coisa a fazer é informar ao Bazel que o arquivo
é uma saída da regra, e não um arquivo temporário usado na implementação
da regra.
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]))]
Confira as funções DefaultInfo
e depset
mais tarde. Por enquanto,
considere que a última linha é a maneira de escolher as saídas de uma regra.
Agora, execute o Bazel:
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
Você gerou um arquivo.
Atributos
Para tornar a regra mais útil, adicione novos atributos usando
o módulo attr
e atualize a definição da regra.
Adicione um atributo de string chamado username
:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
Em seguida, defina no arquivo BUILD
:
foo_binary(
name = "bin",
username = "Alice",
)
Para acessar o valor na função de callback, use ctx.attr.username
. Por
exemplo:
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]))]
Você pode tornar o atributo obrigatório ou definir um valor padrão. Consulte
a documentação de attr.string
.
Você também pode usar outros tipos de atributos, como booleano
ou lista de números inteiros.
Dependências
Atributos de dependência, como attr.label
e attr.label_list
,
declaram uma dependência do destino que tem o atributo para o destino cujo
rótulo aparece no valor do atributo. Esse tipo de atributo forma a base
do gráfico de destino.
No arquivo BUILD
, o rótulo de destino aparece como um objeto de string, como
//pkg:name
. Na função de implementação, o destino ficará acessível como um objeto
Target
. Por exemplo, confira os arquivos retornados
pelo destino usando Target.files
.
Vários arquivos
Por padrão, somente destinos criados por regras podem aparecer como dependências (como um
destino foo_library()
). Se você quiser que o atributo aceite destinos que sejam
arquivos de entrada (como arquivos de origem no repositório), use
allow_files
e especifique a lista de extensões de arquivo aceitas (ou True
para
permitir qualquer extensão de arquivo):
"srcs": attr.label_list(allow_files = [".java"]),
A lista de arquivos pode ser acessada com ctx.files.<attribute name>
. Por
exemplo, a lista de arquivos no atributo srcs
pode ser acessada por
ctx.files.srcs
Arquivo único
Se você precisar de apenas um arquivo, use allow_single_file
:
"src": attr.label(allow_single_file = [".java"])
Esse arquivo pode ser acessado em ctx.file.<attribute name>
:
ctx.file.src
Criar um arquivo com um modelo
Você pode criar uma regra que gera um arquivo .cc com base em um modelo. Além disso, é possível
usar ctx.actions.write
para gerar uma string construída na função de implementação
da regra, mas isso tem dois problemas. Primeiro, conforme o modelo fica
maior, ele se torna mais eficiente para a memória, o que permite colocá-lo em um arquivo separado e evitar
a construção de strings grandes durante a fase de análise. Segundo, usar um arquivo separado
é mais conveniente para o usuário. Em vez disso, use ctx.actions.expand_template
, que executa substituições em um arquivo de modelo.
Crie um atributo template
para declarar uma dependência no arquivo
de modelo:
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,
),
},
)
Os usuários podem usar a regra desta forma:
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
Se você não quiser expor o modelo ao usuário final e sempre usar o mesmo, defina um valor padrão e torne o atributo privado:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
Os atributos que começam com um sublinhado são particulares e não podem ser definidos em um
arquivo BUILD
. O modelo agora é uma dependência implícita: cada destino hello_world
tem uma dependência nesse arquivo. Não se esqueça de tornar esse arquivo visível
para outros pacotes atualizando o arquivo BUILD
e usando
exports_files
:
exports_files(["file.cc.tpl"])
Mais informações
- Consulte a documentação de referência das regras.
- Conheça os depsets.
- Confira o repositório de exemplos, que inclui outros exemplos de regras.