As extensões de módulo permitem que os usuários ampliem o sistema do módulo lendo dados de entrada de módulos no gráfico de dependências, executando a lógica necessária para resolver dependências e, por fim, criando repositórios chamando regras de repositório. Essas extensões têm recursos semelhantes às regras de repositório, o que permite realizar E/S de arquivos, enviar solicitações de rede e assim por diante. Entre outras coisas, eles permitem que o Bazel interaja com outros sistemas de gerenciamento de pacotes, respeitando o gráfico de dependência criado a partir de módulos do Bazel.
É possível definir extensões de módulo em arquivos .bzl
, assim como as regras de repositório. Elas não são invocadas diretamente. Em vez disso, cada módulo especifica dados chamados tags para as extensões lerem. O Bazel executa a resolução do módulo antes de avaliar qualquer extensão. A extensão lê todas as tags pertencentes a ela em todo o gráfico de dependência.
Uso de extensões
As extensões são hospedadas nos próprios módulos do Bazel. Para usar uma extensão em um
módulo, primeiro adicione um bazel_dep
no módulo que hospeda a extensão e, em seguida,
chame a função integrada use_extension
para colocá-la no escopo. Considere o exemplo a seguir: um snippet de um arquivo MODULE.bazel
para usar a extensão "maven" definida no módulo rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Isso vincula o valor de retorno de use_extension
a uma variável, o que permite que o
usuário use a sintaxe de ponto para especificar tags para a extensão. As tags precisam seguir o esquema definido pelas classes de tags correspondentes especificadas na definição da extensão. Veja um exemplo de algumas
tags maven.install
e maven.artifact
:
maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
artifact = "guava",
version = "27.0-jre",
exclusions = ["com.google.j2objc:j2objc-annotations"])
Use a diretiva use_repo
para trazer os repositórios
gerados pela extensão para o escopo do módulo atual.
use_repo(maven, "maven")
Os repositórios gerados por uma extensão fazem parte da API dela. Neste exemplo, a extensão de módulo "maven" promete gerar um repositório chamado maven
. Com a
declaração acima, a extensão resolve corretamente rótulos como
@maven//:org_junit_junit
para apontar para o repositório gerado pela extensão "maven".
Definição da extensão
Você pode definir extensões de módulo de maneira semelhante às regras de repositório, usando a
função module_extension
. No entanto,
embora as regras de repositório tenham vários atributos, as extensões de módulo têm
tag_class
es, cada um com uma série de
atributos. As classes de tag definem esquemas para tags usadas por essa extensão. Por exemplo, a extensão "maven" acima pode ser definida assim:
# @rules_jvm_external//:extensions.bzl
_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
implementation = _maven_impl,
tag_classes = {"install": _install, "artifact": _artifact},
)
Essas declarações mostram que as tags maven.install
e maven.artifact
podem ser especificadas usando o esquema de atributos especificados.
A função de implementação das extensões de módulo é semelhante à das regras
de repositório, exceto pelo fato de receberem um objeto module_ctx
,
que concede acesso a todos os módulos usando a extensão e todas as tags pertinentes.
Em seguida, a função de implementação chama regras de repositório para gerar repositórios.
# @rules_jvm_external//:extensions.bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # a repo rule
def _maven_impl(ctx):
# This is a fake implementation for demonstration purposes only
# collect artifacts from across the dependency graph
artifacts = []
for mod in ctx.modules:
for install in mod.tags.install:
artifacts += install.artifacts
artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]
# call out to the coursier CLI tool to resolve dependencies
output = ctx.execute(["coursier", "resolve", artifacts])
repo_attrs = _process_coursier_output(output)
# call repo rules to generate repos
for attrs in repo_attrs:
http_file(**attrs)
_generate_hub_repo(name = "maven", repo_attrs)
Identidade da extensão
As extensões do módulo são identificadas pelo nome e pelo arquivo .bzl
que aparece
na chamada para use_extension
. No exemplo a seguir, a extensão maven
é identificada pelo arquivo .bzl
@rules_jvm_external//:extension.bzl
e pelo
nome maven
:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Exportar novamente uma extensão de um arquivo .bzl
diferente dá a ela uma nova identidade. Se as duas versões da extensão forem usadas no gráfico do módulo transitivo, elas serão avaliadas separadamente e só verão as tags associadas a essa identidade específica.
Como autor de extensão, verifique se os usuários vão usar sua
extensão do módulo apenas de um único arquivo .bzl
.
Nomes e visibilidade dos repositórios
Os repositórios gerados por extensões têm nomes canônicos no formato module_repo_canonical_name~extension_name~repo_name
. Para extensões hospedadas no
módulo raiz, a parte module_repo_canonical_name
é
substituída pela string _main
. O formato de nome canônico não é uma
API de que você precisa depender. Ele está sujeito a mudanças a qualquer momento.
Essa política de nomenclatura significa que cada extensão tem o próprio "namespace repo". Duas
extensões distintas podem definir um repositório com o mesmo nome sem arriscar
qualquer conflito. Isso também significa que repository_ctx.name
informa o nome canônico
do repositório, que não é o mesmo nome especificado na chamada da regra
de repositório.
Considerando os repositórios gerados por extensões de módulo, há várias regras de visibilidade de repositórios:
- Um repositório de módulos do Bazel pode ver todos os repositórios introduzidos no arquivo
MODULE.bazel
usandobazel_dep
euse_repo
. - Um repositório gerado por uma extensão de módulo pode ver todos os repositórios visíveis para o
módulo que hospeda a extensão, além de todos os outros repositórios gerados pela
mesma extensão do módulo, usando os nomes especificados nas chamadas de regra de repositório como
os nomes aparentes.
- Isso pode resultar em um conflito. Se o repositório do módulo puder ver um repositório com
o nome aparente
foo
e a extensão gerar um repositório com o nome especificadofoo
,foo
vai se referir ao primeiro para todos os repositórios gerados por essa extensão.
- Isso pode resultar em um conflito. Se o repositório do módulo puder ver um repositório com
o nome aparente
Práticas recomendadas
Esta seção descreve as práticas recomendadas ao criar extensões para que elas sejam fáceis de usar, possam ser mantidas e se adaptem bem às mudanças ao longo do tempo.
Colocar cada extensão em um arquivo separado
Quando as extensões estão em arquivos diferentes, isso permite que uma extensão carregue repositórios gerados por outra extensão. Mesmo que você não use essa funcionalidade, é melhor colocá-las em arquivos separados, caso precise delas mais tarde. Isso ocorre porque a identificação da extensão é baseada no arquivo dela. Portanto, mover a extensão para outro arquivo posteriormente altera sua API pública e é uma alteração incompatível com versões anteriores para seus usuários.
Especificar o sistema operacional e a arquitetura
Se a extensão depender do sistema operacional ou do tipo de arquitetura, indique isso na definição da extensão usando os atributos booleanos "os_dependent" e "arch_dependent". Isso garante que o Bazel reconheça a necessidade de reavaliação se houver alterações em qualquer um deles.