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 do repositório, o que permite que elas realizem E/S de arquivos, enviem 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ências criado com módulos do Bazel.
É possível definir extensões de módulo em arquivos .bzl
, assim como regras de repositório. Eles não são invocados diretamente. Em vez disso, cada módulo especifica partes de dados chamadas de tags para as extensões lerem. O Bazel executa a resolução de módulos 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 depois
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, permitindo 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 correspondentes especificadas na definição de extensão. Exemplo especificando 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 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 do 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 de extensão
É possível definir extensões de módulo de maneira semelhante às regras de repositório usando a função module_extension
. No entanto, enquanto as regras de repositório têm vários atributos, as extensões de módulo têm tag_class
es, cada uma com vários atributos. As classes de tag definem esquemas para tags usadas por esta
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 atributo especificado.
A função de implementação das extensões de módulo é semelhante às regras de repositório, exceto que elas recebem um objeto module_ctx
, que concede acesso a todos os módulos usando a extensão e todas as tags relevantes.
A função de implementação chama as regras do 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 atribui a ela uma nova identidade. Se as duas versões da extensão forem usadas no gráfico de módulo transitivo, 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 usem a extensão de módulo em apenas um arquivo .bzl
.
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
. O formato do nome canônico não é uma API de que 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 do repositório". Duas
extensões distintas podem definir um repositório com o mesmo nome sem risco de
conflitos. Isso também significa que repository_ctx.name
informa o nome canônico do repositório, que não é o mesmo especificado na chamada da regra do repositório.
Considerando os repositórios gerados por extensões de módulo, há várias regras de visibilidade:
- Um repositório de módulo do Bazel pode ver todos os repositórios introduzidos no arquivo
MODULE.bazel
viabazel_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 de módulo (usando os nomes especificados nas chamadas de regra do repositório como
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
, então, para todos os repositórios gerados por essa extensão,foo
se refere ao primeiro.
- Isso pode resultar em um conflito. Se o repositório do módulo puder ver um repositório com o nome aparente
- Da mesma forma, em uma função de implementação de extensão de módulo, os repositórios criados
pela extensão podem se referir uns aos outros pelos nomes aparentes em
atributos, independente da ordem em que são criados.
- Em caso de conflito com um repositório visível para o módulo, os rótulos
transmitidos aos atributos de regra do repositório podem ser encapsulados em uma chamada para
Label
para garantir que eles se refiram ao repositório visível para o módulo em vez do repositório gerado pela extensão com o mesmo nome.
- Em caso de conflito com um repositório visível para o módulo, os rótulos
transmitidos aos atributos de regra do repositório podem ser encapsulados em uma chamada para
Substituir e injetar repositórios de extensão de módulo
O módulo raiz pode usar override_repo
e inject_repo
para substituir ou injetar repositórios de extensão de módulo.
Exemplo: substituir o java_tools
de rules_java
por uma cópia vendida
# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
name = "my_java_tools",
path = "vendor/java_tools",
)
bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
Exemplo: adicione um patch a uma dependência do Go para depender de @zlib
em vez do zlib do sistema.
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
patches = [
"//patches:my_module_zlib.patch",
],
path = "example.com/my_module",
)
use_repo(go_deps, ...)
inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
go_binary(
name = "my_module",
importpath = "example.com/my_module",
srcs = ["my_module.go"],
- copts = ["-lz"],
+ cdeps = ["@zlib"],
)
Práticas recomendadas
Esta seção descreve as práticas recomendadas para escrever extensões que sejam fáceis de usar e manter, além de se adaptarem 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 colocar os elementos em arquivos separados caso precise deles depois. Isso acontece porque a identidade da extensão se baseia no arquivo dela. Portanto, mover a extensão para outro arquivo depois muda a API pública e é uma mudança incompatível com versões anteriores para os usuários.
Especificar a reprodutibilidade
Se a extensão sempre definir os mesmos repositórios com as mesmas entradas (tags de extensão, arquivos que ela lê etc.) e, em particular, não depender de downloads que não são protegidos por um checksum, considere retornar extension_metadata
com reproducible = True
. Isso permite que o Bazel pule essa extensão ao gravar no
arquivo de bloqueio.
Especificar o sistema operacional e a arquitetura
Se a extensão depender do sistema operacional ou do tipo de arquitetura dele,
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 mudanças em qualquer um deles.
Como esse tipo de dependência do host dificulta a manutenção da entrada do arquivo de bloqueio para essa extensão, considere marcar a extensão como reproduzível, se possível.
Somente o módulo raiz pode afetar diretamente os nomes dos repositórios.
Quando uma extensão cria repositórios, eles são criados no namespace dela. Isso significa que podem ocorrer colisões se módulos diferentes usarem a mesma extensão e acabarem criando um repositório com o mesmo nome. Isso geralmente se manifesta como um tag_class
de uma extensão de módulo com um argumento name
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")
, ambos tentarão 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 essa capacidade porque nada vai depender dele. Assim, não é preciso se preocupar com outro módulo criando um nome conflitante.