A interação diária com o Bazel acontece principalmente por alguns comandos:
build, test e run. No entanto, às vezes, eles podem parecer limitados: talvez você queira enviar pacotes para um repositório, publicar documentação para usuários finais ou implantar um aplicativo com o Kubernetes. Mas o Bazel não tem um publish ou
deploy comando. Onde essas ações se encaixam?
O comando bazel run
O foco do Bazel na hermeticidade, na capacidade de reprodução e na incrementalidade significa que os comandos
build e test não são úteis para as tarefas acima. Essas ações
podem ser executadas em um sandbox, com acesso limitado à rede, e não há garantia de que serão
executadas novamente com cada bazel build.
Em vez disso, use bazel run: o cavalo de batalha para tarefas que você quer ter
efeitos colaterais. Os usuários do Bazel estão acostumados a regras que criam executáveis, e
os autores de regras podem seguir um conjunto comum de padrões para estender isso a
"verbos personalizados".
Na prática: rules_k8s
Por exemplo, considere rules_k8s,
as regras do Kubernetes para o Bazel. Suponha que você tenha o seguinte destino:
# BUILD file in //application/k8s
k8s_object(
name = "staging",
kind = "deployment",
cluster = "testing",
template = "deployment.yaml",
)
A regra k8s_object cria um arquivo YAML padrão do Kubernetes quando bazel build é usado no destino staging
. No entanto, os destinos adicionais também são criados pela k8s_object
macro com nomes como staging.apply e :staging.delete. Esses scripts de build
executam essas ações e, quando executados com bazel run
staging.apply, eles se comportam como nossos próprios comandos bazel k8s-apply ou bazel
k8s-delete.
Outro exemplo: ts_api_guardian_test
Esse padrão também pode ser visto no projeto Angular. A
ts_api_guardian_test macro
produz dois destinos. O primeiro é um destino nodejs_test padrão que compara
algumas saídas geradas com um arquivo "dourado" (ou seja, um arquivo que contém a
saída esperada). Ele pode ser criado e executado com uma invocação normal de bazel
test. Em angular-cli, é possível executar um desses
destinos
com bazel test //etc/api:angular_devkit_core_api.
Com o tempo, esse arquivo dourado pode precisar ser atualizado por motivos legítimos.
A atualização manual é cansativa e propensa a erros. Portanto, essa macro também fornece
um destino nodejs_binary que atualiza o arquivo dourado, em vez de compará-lo
. Efetivamente, o mesmo script de teste pode ser gravado para ser executado no modo "verificar"
ou "aceitar", com base em como ele é invocado. Isso segue o mesmo padrão
que você já aprendeu: não há um comando bazel test-accept nativo, mas o
mesmo efeito pode ser alcançado com
bazel run //etc/api:angular_devkit_core_api.accept.
Esse padrão pode ser muito útil e bastante comum quando você aprende a reconhecê-lo.
Como adaptar suas próprias regras
As macros são o coração desse padrão. Elas são usadas como regras, mas podem criar vários destinos. Normalmente, elas criam um destino com o nome especificado que executa a ação de build principal: talvez ele crie um binário normal, uma imagem do Docker ou um arquivo de código-fonte. Nesse padrão, destinos adicionais são criados para produzir scripts que executam efeitos colaterais com base na saída do destino principal, como publicar o binário resultante ou atualizar a saída de teste esperada.
Para ilustrar isso, encapsule uma regra imaginária que gera um site com Sphinx com uma macro para criar um destino adicional que permita ao usuário publicá-lo quando estiver pronto. Considere a seguinte regra para gerar um site com o Sphinx:
_sphinx_site = rule(
implementation = _sphinx_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)
Em seguida, considere uma regra como a seguinte, que cria um script que, quando executado, publica as páginas geradas:
_sphinx_publisher = rule(
implementation = _publish_impl,
attrs = {
"site": attr.label(),
"_publisher": attr.label(
default = "//internal/sphinx:publisher",
executable = True,
),
},
executable = True,
)
Por fim, defina a seguinte macro simbólica (disponível no Bazel 8 ou mais recente) para criar destinos para as duas regras acima juntas:
def _sphinx_site_impl(name, visibility, srcs, **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML. We
# set `visibility = visibility` to make it visible to callers of the
# macro.
_sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above. We don't want it to be visible to callers of
# our macro, so we omit visibility for it.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
sphinx_site = macro(
implementation = _sphinx_site_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
# Inherit common attributes like tags and testonly
inherit_attrs = "common",
)
Se você precisar oferecer suporte a versões do Bazel mais antigas que a 8, defina uma macro legada:
def sphinx_site(name, srcs = [], **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML.
_sphinx_site(name = name, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
Nos arquivos BUILD, use a macro como se ela apenas criasse o destino principal
target:
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
Neste exemplo, um destino "docs" é criado, como se a macro fosse uma
regra única padrão do Bazel. Quando criada, a regra gera algumas configurações
e executa o Sphinx para produzir um site HTML, pronto para inspeção manual. No entanto,
um destino "docs.publish" adicional também é criado, que cria um script para
publicar o site. Depois de verificar a saída do destino principal, você pode
usar bazel run :docs.publish para publicá-lo para consumo público, como
um comando bazel publish imaginário.
Não é imediatamente óbvio como a implementação da _sphinx_publisher
regra pode ser. Muitas vezes, ações como essa gravam um script de shell launcher.
Esse método normalmente envolve o uso
ctx.actions.expand_template
para gravar um script de shell muito simples, nesse caso, invocando o binário do editor
com um caminho para a saída do destino principal. Dessa forma, a implementação do editor
pode permanecer genérica, a regra _sphinx_site pode apenas produzir
HTML, e esse pequeno script é tudo o que é necessário para combinar os dois.
Em rules_k8s, é isso que .apply faz:
expand_template
grava um script Bash muito simples, com base em
apply.sh.tpl,
que executa kubectl com a saída do destino principal. Esse script pode
ser criado e executado com bazel run :staging.apply, fornecendo um
k8s-apply comando para k8s_object destinos.