A interação diária com o Bazel acontece principalmente por alguns comandos: build
, test
e run
. No entanto, às vezes, esses recursos 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 comando publish
ou deploy
. Onde essas ações se encaixam?
O comando "bazel run"
O foco do Bazel em hermeticidade, reprodutibilidade e 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 a 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 natureza: rules_k8s
Por exemplo, considere rules_k8s
, as regras do Kubernetes para Bazel. Suponha que você tenha a seguinte meta:
# 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 macro k8s_object
com nomes como staging.apply
e :staging.delete
. Esses scripts de build realizam essas ações e, quando executados com bazel run
staging.apply
, 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
macro ts_api_guardian_test
produz dois destinos. O primeiro é um destino nodejs_test
padrão que compara
algumas saídas geradas com um arquivo "ouro", 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.
Atualizar isso manualmente é cansativo e propenso a erros. Por isso, essa macro também fornece um destino nodejs_binary
que atualiza o arquivo de referência, em vez de comparar com ele. Na prática, o mesmo script de teste pode ser escrito para ser executado no modo "verify"
ou "accept", 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 eficiente e se torna bastante comum quando você aprende a reconhecê-lo.
Adaptar suas próprias regras
As macros são a base desse padrão. As macros são usadas como regras, mas podem criar vários destinos. Normalmente, eles 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, outros destinos são criados para produzir scripts que realizam 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 publicar 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",
)
Ou, se você precisar oferecer suporte a versões do Bazel anteriores à 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:
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
Neste exemplo, um destino "docs" é criado, como se a macro fosse uma
regra única e 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, o que gera um script para
publicar o site. Depois de verificar a saída do destino principal, use bazel run :docs.publish
para publicar o conteúdo para consumo público, assim como um comando bazel publish
imaginário.
Não é imediatamente óbvio como seria a implementação da regra _sphinx_publisher
. Muitas vezes, ações como essa gravam um script shell de iniciador.
Esse método normalmente envolve o uso de
ctx.actions.expand_template
para gravar um script shell muito simples, neste 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
juntos.
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 efetivamente um
comando k8s-apply
para destinos k8s_object
.