O Starlark é uma biblioteca semelhante a Python
linguagem de configuração desenvolvida originalmente para uso no Bazel e adotada desde a adoção
por outras ferramentas. Os arquivos BUILD
e .bzl
do Bazel são escritos em um dialeto do
O Starlark é conhecido como "Build Language", embora seja muitas vezes simplesmente
também conhecido como "Starlark", especialmente ao enfatizar que um atributo é
expresso na linguagem de criação em vez de ser uma linguagem de programação parte
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 A documentação do Bazel e do Starlark (links em inglês) para mais detalhes, e a Modelo SIG de regras como um ponto de partida para novos conjuntos de regras.
A 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ê
podemos deixar a função em branco por enquanto. O argumento ctx
fornece informações sobre o alvo.
É possível carregar e usar a regra 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 regras: ela tem um
obrigatório, ele aceita atributos comuns, como visibility
, testonly
e
tags
Modelo de avaliação
Antes de prosseguir, é importante entender como o código é avaliado.
Atualize foo.bzl
com algumas instruções de exibição:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
e BUILD:
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. você pode encontrar uma lista completa no
Referência da API.
Consulte 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:
- "avaliação de arquivo bzl" é impresso primeiro. Antes de avaliar o arquivo
BUILD
, O Bazel avalia todos os arquivos que carrega. Se vários arquivosBUILD
estiverem sendo carregados foo.bzl, você veria apenas uma ocorrência de "avaliação de arquivo bzl" porque O Bazel armazena o resultado da avaliação em cache. - A função de callback
_foo_binary_impl
não é chamada. Carregamentos de consulta do BazelBUILD
, mas não analisa destinos.
Para analisar os destinos, use o cquery
("configurado"
consulta") 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 a opção
"avaliação de arquivo bzl" nem "BUILD file" são impressas 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 sua regra mais útil, atualize-a para gerar um arquivo. Primeiro, declare e dê um nome a ele. Neste exemplo, crie um arquivo com o mesmo nome de o destino:
ctx.actions.declare_file(ctx.label.name)
Se você executar bazel build :all
agora, um erro será exibido:
The following files have no generating action:
bin2
Sempre que você declarar um arquivo, precisará informar ao Bazel como gerá-lo.
criar uma ação. Use ctx.actions.write
,
para criar um arquivo com o conteúdo determinado.
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 ao Bazel
como gerar o arquivo. Mas o Bazel não cria o arquivo até que ele seja
realmente seja solicitado. A última coisa a fazer é dizer ao Bazel que o arquivo
é uma saída da regra, e não um arquivo temporário usado dentro dela
implementação.
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]))]
Observe as funções DefaultInfo
e depset
mais tarde. Por enquanto,
presuma que a última linha é a maneira de escolher os resultados 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 com sucesso!
Atributos
Para tornar a regra mais útil, adicione novos atributos usando
módulo attr
e atualize a definição da regra.
Adicione um atributo de string com o nome username
:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
Em seguida, defina-o 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. Confira
a documentação de attr.string
.
Você também pode usar outros tipos de atributos, como boolean
ou lista de números inteiros.
Dependências
Atributos de dependência, como attr.label
e attr.label_list
,
declarar uma dependência do destino que possui o atributo para o destino cuja
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 será acessível como um
Objeto Target
. Por exemplo, veja 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
foo_library()
de destino). Se você quiser que o atributo aceite destinos que estejam
arquivos de entrada (como arquivos de origem no repositório), você pode fazer isso com
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>
. Para
exemplo, a lista de arquivos no atributo srcs
pode ser acessada pela
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 gere um arquivo .cc com base em um modelo. Além disso, você
pode usar ctx.actions.write
para gerar uma string criada na regra.
função de implementação, mas isso tem dois problemas. Primeiro, à medida que o modelo
maior, torna-se mais eficiente em memória colocá-lo em um arquivo separado e evitar
construção de strings grandes durante a fase de análise. Em segundo lugar, usar um método
é mais conveniente para o usuário. Em vez disso, use
ctx.actions.expand_template
,
que executa substituições em um arquivo modelo.
Crie um atributo template
para declarar uma dependência no modelo.
arquivo:
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 é possível definir um valor padrão e tornar o atributo privado:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
Os atributos que começam com um sublinhado são privados e não podem ser definidos em uma
BUILD
. O modelo agora é uma dependência implícita: cada hello_world
target depende desse arquivo. Não se esqueça de tornar este arquivo visível
para outros pacotes atualizando o arquivo BUILD
e usando
exports_files
:
exports_files(["file.cc.tpl"])
Indo mais longe
- Consulte a documentação de referência das regras.
- Familiarize-se com as dependências.
- Confira o repositório de exemplos. que inclui outros exemplos de regras.