As extensões de módulo permitem que os usuários estendam o sistema de módulos lendo dados de entrada de módulos em todo o 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 que elas executem E/S de arquivos, enviem solicitações de rede e assim por diante. Entre outras coisas, elas permitem que o Bazel interaja com outros sistemas de gerenciamento de pacotes, respeitando o gráfico de dependências criado com módulos do Bazel.
É possível definir extensões de módulo em .bzl arquivos, assim como regras de repositório. Elas não são invocadas diretamente. Em vez disso, cada módulo especifica partes de dados chamadas tags
para as extensões lerem. O Bazel executa a resolução de módulos antes de avaliar qualquer
extensões. A extensão lê todas as tags pertencentes a ela em todo o
gráfico de dependências.
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
MODULE.bazel arquivo para usar a extensão "maven" definida no
rules_jvm_external
módulo:
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 tag correspondidas especificadas na
definição da extensão. Confira um exemplo que especifica algumas
maven.install e maven.artifact tags:
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 colocar os repositórios
gerados pela extensão no 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
"maven" extensão de módulo 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 de extensão
É possível definir extensões de módulo de maneira semelhante às regras de repositório, usando a
module_extension função. No entanto,
enquanto as regras de repositório têm vários atributos, as extensões de módulo têm
tag_classes, cada uma com vários
atributos. As classes de tag definem esquemas para tags usadas por essa extensão. Por
exemplo, a extensão "maven" acima pode ser definida desta forma:
# @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 atributo especificado.
A função de implementação de extensões de módulo é semelhante à das regras de repositório, exceto que elas recebem um module_ctx objeto,
que concede acesso a todos os módulos que usam a extensão e a todas as tags relevantes.
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 de 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")
A reexportação de uma extensão de um arquivo .bzl diferente confere a ela uma nova identidade
e se as duas versões da extensão forem usadas no gráfico de módulos transitivos,
elas serão avaliadas separadamente e só vão mostrar as tags associadas
a essa identidade específica.
Como autor de uma extensão, você precisa garantir que os usuários só usem a
extensão de módulo de um único .bzl arquivo.
Nomes e visibilidade do repositório
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 da qual você deve depender. Ele está sujeito a mudanças a qualquer momento.
Essa política de nomenclatura significa que cada extensão tem seu próprio "namespace de repositório". Duas
extensões distintas podem definir um repositório com o mesmo nome sem correr o risco de
conflitos. 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ório:
- Um repositório de módulo do Bazel pode mostrar todos os repositórios introduzidos no arquivo
MODULE.bazelusandobazel_depeuse_repo. - Um repositório gerado por uma extensão de módulo pode mostrar 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 de módulo (usando os nomes especificados nas chamadas de regra de repositório como
nomes aparentes).
- Isso pode resultar em um conflito. Se o repositório do módulo puder mostrar um repositório com
o nome aparente
fooe a extensão gerar um repositório com o nome especificadofoo, então, para todos os repositórios gerados por essa extensão,foovai se referir ao primeiro.
- Isso pode resultar em um conflito. Se o repositório do módulo puder mostrar um repositório com
o nome aparente
Práticas recomendadas
Esta seção descreve as práticas recomendadas ao escrever extensões para que elas sejam simples de usar, fáceis de manter e se adaptem bem às mudanças ao longo do tempo.
Coloque cada extensão em um arquivo separado
Quando as extensões estão em arquivos diferentes, uma extensão pode carregar repositórios gerados por outra. Mesmo que você não use essa funcionalidade, é melhor colocá-las em arquivos separados caso precise delas mais tarde. Isso ocorre porque a identidade da extensão é baseada no arquivo dela. Portanto, mover a extensão para outro arquivo mais tarde muda sua API pública e é uma mudança incompatível com versões anteriores para seus usuários.
Especificar o sistema operacional e a arquitetura
Se a extensão depende do sistema operacional ou do tipo de arquitetura,
indique isso na definição da extensão usando os os_dependent
e arch_dependent atributos booleanos. Isso garante que o Bazel reconheça a
necessidade de reavaliação se houver mudanças em um deles.
Somente o módulo raiz pode afetar diretamente os nomes dos repositórios
Quando uma extensão cria repositórios, eles são criados dentro
do namespace da extensão. Isso significa que colisões podem ocorrer se módulos diferentes
usarem a mesma extensão e acabarem criando um repositório com o mesmo
nome. Isso geralmente se manifesta como uma extensão de módulo tag_class com um argumento name
que é transmitido como o valor name de uma regra de repositório.
Por exemplo, digamos que o módulo raiz, A, dependa do módulo B. Os dois módulos
dependem do módulo mylang. Se A e B chamarem
mylang.toolchain(name="foo"), os dois vão tentar criar um repositório chamado
foo no módulo mylang, e um erro vai ocorrer.
Para evitar isso, remova a capacidade de definir o nome do repositório diretamente, ou permita que apenas o módulo raiz faça isso. Não há problema em permitir que o módulo raiz tenha esta capacidade, porque nada vai depender dele. Portanto, não é preciso se preocupar com outro módulo criando um nome conflitante.