Bzlmod é o codinome do novo sistema de dependência externa. introduzido no Bazel 5.0. Ele foi introduzido para resolver vários aspectos problemáticos da sistema antigo que não podia ser corrigido de forma incremental, consulte Seção "Definição do problema" do documento de design original para mais detalhes.
No Bazel 5.0, o Bzlmod não está ativado por padrão. a bandeira
--experimental_enable_bzlmod
precisa ser especificado para que o seguinte aconteça
efeito Como o nome da sinalização sugere, no momento, esse recurso é experimental.
As APIs e os comportamentos podem mudar até o lançamento oficial do recurso.
Para migrar seu projeto para o Bzlmod, siga o Guia de migração do Bzlmod. Você também pode encontrar exemplos de usos do Bzlmod no repositório examples.
Módulos do Bazel
O antigo sistema de dependência externa baseado em WORKSPACE
é centralizado na
repositories (ou repos), criados por meio de regras de repositório (ou regras de repositório).
Embora os repositórios ainda sejam um conceito importante no novo sistema, os módulos são a
unidades centrais de dependência.
Um módulo é essencialmente um projeto do Bazel que pode ter várias versões, cada uma que publica metadados sobre outros módulos de que depende. Isso é análogo a conceitos conhecidos em outros sistemas de gerenciamento de dependências: um artifact, um pacote do npm, uma caixa do Cargo, um módulo Go etc.
Um módulo simplesmente especifica as próprias dependências usando pares name
e version
.
em vez de URLs específicos em WORKSPACE
. Em seguida, as dependências são pesquisadas
Um registro do Bazel por padrão, os
Bazel Central Registry (em inglês). No seu espaço de trabalho, cada
é transformado em um repositório.
MODULE.bazel
Cada versão de cada módulo tem um arquivo MODULE.bazel
que declara
dependências e outros metadados. Este é um exemplo básico:
module(
name = "my-module",
version = "1.0",
)
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
O arquivo MODULE.bazel
precisa estar localizado na raiz do diretório do espaço de trabalho.
(ao lado do arquivo WORKSPACE
). Diferentemente do arquivo WORKSPACE
, não é necessário
para especificar suas dependências transitivas. você só deve especificar
dependências diretas, e os arquivos MODULE.bazel
das dependências são
para descobrir dependências transitivas automaticamente.
O arquivo MODULE.bazel
é semelhante aos arquivos BUILD
, porque não tem suporte
forma de fluxo de controle, também proíbe instruções load
. As diretivas
Os arquivos MODULE.bazel
compatíveis são:
module
, para especificar metadados sobre o módulo atual, incluindo nome, versão etc.bazel_dep
, para especificar dependências em outros módulos do Bazel.- Substituições, que só podem ser usadas pelo módulo raiz (ou seja, não por um que está sendo usado como uma dependência) para personalizar o comportamento de um alguma dependência direta ou transitiva:
- Diretivas relacionadas às extensões de módulo:
Formato da versão
O Bazel tem um ecossistema diverso, e os projetos usam vários esquemas de controle de versões. A
o mais popular é o SemVer, mas há
também projetos de destaque usando diferentes esquemas,
Abseil, cuja
são baseadas em data, por exemplo, 20210324.2
).
Por esse motivo, o Bzlmod adota uma versão mais descontraída das especificações do SemVer. A as diferenças incluem:
- O SemVer determina que o "lançamento" da versão deve consistir em 3
segmentos:
MAJOR.MINOR.PATCH
. No Bazel, esse requisito é reduzido para que que qualquer número de segmentos é permitido. - No SemVer, cada um dos segmentos do "lançamento" deve ter apenas dígitos. No Bazel, isso é flexibilizado para permitir letras também, e a comparação a semântica corresponde aos "identifiers" no "pré-lançamento", parte.
- Além disso, a semântica dos aumentos principais, secundários e de versão de patch são é aplicada. No entanto, consulte o nível de compatibilidade para detalhes sobre como indicamos a compatibilidade com versões anteriores.
Qualquer versão válida do SemVer é uma versão válida do módulo do Bazel. Além disso, dois
As versões a
e b
do SemVer comparam a < b
se as mesmas forem mantidas quando forem
em comparação às versões do módulo do Bazel.
Resolução da versão
O problema da dependência losango é um item básico da dependência com controle de versões. de gerenciamento de projetos. Suponha que você tenha o seguinte gráfico de dependência:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
Qual versão de D deve ser usada? Para resolver essa questão, o Bzlmod usa o Seleção de versão mínima (MVS) introduzido no sistema de módulos Go. O MVS presume que todos os novos de um módulo são compatíveis com versões anteriores e, assim, escolhe a especificada por qualquer dependente (D 1.1 no nosso exemplo). É chamado de "mínimo" porque, aqui, o D 1.1 é a versão mínima que pode atender aos nossos requisitos. mesmo que haja D 1.2 ou mais recente, não os selecionamos. Isso tem o benefício extra que a seleção da versão seja de alta fidelidade e reprodutível.
A resolução de versão é realizada localmente na sua máquina, não pelo registro.
Nível de compatibilidade
A suposição do MVS sobre a compatibilidade com versões anteriores é viável porque trata as versões incompatíveis com versões anteriores de um módulo como um módulo separado. Em relação ao SemVer, isso significa que A 1.x e A 2.x são considerados módulos distintos, e podem coexistir no gráfico de dependências resolvida. Isso é, por sua vez, feito possível porque a versão principal está codificada no caminho do pacote no Vá para que não haja conflitos de tempo de compilação ou vinculação.
No Bazel, não temos essas garantias. Portanto, precisamos de uma maneira de denotar a "maioria
versão" para detectar versões incompatíveis com versões anteriores. Este número
é chamada de nível de compatibilidade e é especificada por cada versão de módulo em
à diretiva module()
. Com essas informações em mãos, podemos lançar um erro
quando detectamos que as versões do mesmo módulo com compatibilidade
existem no gráfico de dependências resolvidas.
Nomes de repositório
No Bazel, cada dependência externa tem um nome de repositório. Às vezes, o mesmo
pode ser usada com diferentes nomes de repositório (por exemplo, ambos
Média de @io_bazel_skylib
e @bazel_skylib
Bazel skylib) ou a mesma
de repositório de dados pode ser usado para dependências diferentes em projetos distintos.
No Bzlmod, os repositórios podem ser gerados por módulos do Bazel e extensões de módulo. Para resolver conflitos de nome de repositório, estamos adotando o mapeamento de repositório no novo sistema. Aqui estão dois conceitos importantes:
Nome do repositório canônico: o nome do repositório globalmente exclusivo para cada repositório de dados. Esse será o nome do diretório em que o repositório está.
Ele é construído da seguinte forma. Aviso: o formato de nome canônico é não é uma API de que você deveria depender, ela está sujeita a alterações a qualquer momento):- Para repositórios do módulo do Bazel:
module_name~version
(Exemplo:@bazel_skylib~1.0.3
) - Para repositórios de extensão de módulo:
module_name~version~extension_name~repo_name
(Exemplo:@rules_cc~0.0.1~cc_configure~local_config_cc
)
- Para repositórios do módulo do Bazel:
Nome do repositório aparente: o nome do repositório que será usado nas APIs
BUILD
e.bzl
em um repositório. A mesma dependência pode ter valores aparentes diferentes em repositórios diferentes.
É determinado da seguinte maneira:
Cada repositório tem um dicionário de mapeamento de repositório de suas dependências diretas,
que é um mapa do nome do repositório aparente para o nome do repositório canônico.
Usamos o mapeamento do repositório para resolver o nome do repositório ao construir um
rótulo. Observe que não há conflito entre os nomes dos repositórios canônicos e os
os usos de nomes de repositórios aparentes podem ser descobertos analisando o MODULE.bazel
arquivo, portanto os conflitos podem ser facilmente detectados e resolvidos sem afetar
outras dependências.
Dependências estritas
O novo formato de especificação de dependência nos permite realizar verificações mais rigorosas. Em particular, agora vamos impor que um módulo só pode usar repositórios criados a partir dependências diretas. Isso ajuda a evitar falhas acidentais e difíceis de depurar quando algo no gráfico de dependências transitivas muda.
As dependências estritas são implementadas com base mapeamento do repositório. Basicamente, a o mapeamento do repositório de cada repo contém todas as suas dependências diretas, quaisquer outro repositório não fica visível. As dependências visíveis para cada repositório são determinado da seguinte forma:
- Um repositório de módulo do Bazel pode conferir todos os repositórios introduzidos no arquivo
MODULE.bazel
. viabazel_dep
euse_repo
- Um repositório de extensão de módulos pode conferir todas as dependências visíveis do módulo que fornece a extensão, além de todos os outros repositórios gerados pelo mesmo módulo. .
Registros
O Bzlmod descobre dependências solicitando as informações dele ao Bazel registros. Um registro do Bazel é simplesmente um banco de dados de módulos do Bazel. O único com suporte é um registro de índice, que é um diretório local ou um servidor HTTP estático seguindo um formato específico. Na no futuro, planejamos adicionar suporte para registros de módulo único, que são simplesmente Repositórios Git que contêm a origem e o histórico de um projeto.
Registro do índice
Um registro de índice é um diretório local ou um servidor HTTP estático que contém
informações sobre uma lista de módulos, incluindo sua página inicial, mantenedores, o
MODULE.bazel
de cada versão e como buscar a origem de cada uma
para a versão anterior. Ele não precisa disponibilizar os arquivos de origem em si.
Um registro de índice precisa seguir o formato abaixo:
/bazel_registry.json
: um arquivo JSON contendo metadados do registro, como:mirrors
, especificando a lista de espelhos a serem usados para arquivos de origem.module_base_path
, especificando o caminho base para módulos comlocal_repository
no arquivosource.json
.
/modules
: um diretório que contém um subdiretório para cada módulo no de registros./modules/$MODULE
: um diretório contendo um subdiretório para cada versão deste módulo, além do seguinte arquivo:metadata.json
: um arquivo JSON contendo informações sobre o módulo. pelos seguintes campos:homepage
: o URL da página inicial do projeto.maintainers
: uma lista de objetos JSON, cada um correspondendo ao as informações de um administrador do módulo no registro. Observe que não é necessariamente o mesmo que os autores da em um projeto de IA.versions
: uma lista de todas as versões deste módulo que podem ser encontradas em neste registro.yanked_versions
: uma lista de versões ianizadas deste módulo. Isso atualmente é um ambiente autônomo, mas, no futuro, versões puxadas serão pulou ou produziu um erro.
/modules/$MODULE/$VERSION
: um diretório que contém os seguintes arquivos:MODULE.bazel
: o arquivoMODULE.bazel
dessa versão do módulo.source.json
: um arquivo JSON contendo informações sobre como buscar o origem dessa versão do módulo.- O tipo padrão é "arquivo" pelos seguintes campos:
url
: o URL do arquivo de origem.integrity
: o Integridade de sub-recursos checksum do arquivo.strip_prefix
: um prefixo de diretório a ser removido ao extrair o arquivo de origem.patches
: uma lista de strings, e cada uma nomeia um arquivo de patch para aplicar ao arquivo extraído. Os arquivos de patch estão localizados em diretório/modules/$MODULE/$VERSION/patches
.patch_strip
: igual ao argumento--strip
do patch do Unix.
- O tipo pode ser alterado para usar um caminho local com estes campos:
type
:local_path
path
: o caminho local para o repo, calculado da seguinte maneira:- Se o caminho for absoluto, será usado do jeito que está.
- Se o caminho for relativo e
module_base_path
for absoluto, o caminho está resolvido para<module_base_path>/<path>
- Se o caminho e
module_base_path
forem ambos caminhos relativos, o caminho será resolvido para<registry_path>/<module_base_path>/<path>
. O registro precisa ser hospedado localmente e usado por--registry=file://<registry_path>
. Caso contrário, o Bazel vai gerar um erro.
- O tipo padrão é "arquivo" pelos seguintes campos:
patches/
: um diretório opcional que contém arquivos de patch, usado somente quandosource.json
tem "archive". não é válido.
Registro central do Bazel
O Bazel Central Registry (BCR) é um registro de índice localizado em
bcr.bazel.build. Seu conteúdo
têm o suporte do repositório do GitHub
bazelbuild/bazel-central-registry
O BCR é mantido pela comunidade do Bazel. os colaboradores podem enviar solicitações de envio. Consulte Políticas e procedimentos de registro central do Bazel.
Além de seguir o formato de um registro de índice normal, o BCR exige
um arquivo presubmit.yml
para cada versão do módulo
/modules/$MODULE/$VERSION/presubmit.yml
). Esse arquivo especifica alguns elementos
destinos de build e teste que podem ser usados para verificar a integridade
do módulo e é usada pelos pipelines de CI do BCR para garantir a interoperabilidade
entre os módulos no BCR.
Seleção de registros
A flag repetível --registry
do Bazel pode ser usada para especificar a lista de
registros dos quais solicitar módulos. Assim, você pode configurar seu projeto para buscar
dependências de um registro interno ou de terceiros. Registros anteriores levam
precedência. Por conveniência, você pode colocar uma lista de sinalizações --registry
no
.bazelrc
do seu projeto.
Extensões do módulo
As extensões de módulo permitem ampliar o sistema de módulos lendo dados de entrada.
dos módulos em todo o gráfico de dependências, executando a lógica necessária para resolver
dependências e, por fim, criar repositórios chamando regras de repo. Elas são semelhantes
função às macros WORKSPACE
atuais, mas são mais adequadas ao mundo
módulos e dependências transitivas.
As extensões do módulo são definidas em arquivos .bzl
, assim como as regras de repositório ou
WORKSPACE
macros. Elas não são invocadas diretamente. Em vez disso, cada módulo pode
especificar partes de dados chamadas tags para que as extensões leiam. Depois do módulo,
a resolução da versão seja concluída, as extensões do módulo serão executadas. Cada extensão é executada
uma vez após a resolução do módulo (ainda antes de qualquer compilação realmente acontecer) e
consegue ler todas as tags pertencentes a ele em todo o gráfico de dependências.
[ A 1.1 ]
[ * maven.dep(X 2.1) ]
[ * maven.pom(...) ]
/ \
bazel_dep / \ bazel_dep
/ \
[ B 1.2 ] [ C 1.0 ]
[ * maven.dep(X 1.2) ] [ * maven.dep(X 2.1) ]
[ * maven.dep(Y 1.3) ] [ * cargo.dep(P 1.1) ]
\ /
bazel_dep \ / bazel_dep
\ /
[ D 1.4 ]
[ * maven.dep(Z 1.4) ]
[ * cargo.dep(Q 1.1) ]
No exemplo de gráfico de dependência acima, A 1.1
, B 1.2
etc. são módulos do Bazel.
Pense em cada um como um arquivo MODULE.bazel
. Cada módulo pode especificar
para extensões de módulo. alguns são especificados para a extensão "maven",
e outros são especificados para "cargo". Quando esse gráfico de dependência é finalizado (por
exemplo, talvez o B 1.2
realmente tenha um bazel_dep
em D 1.3
, mas foi atualizado para
D 1.4
devido a C
), a extensão "maven" é executado e lê todos os
Tags maven.*
, usando as informações contidas neles para decidir quais repositórios criar.
Da mesma forma, para a tag "cargo", .
Uso da extensão
As extensões são hospedadas nos próprios módulos do Bazel. Portanto, para usar uma extensão em
seu módulo, primeiro você precisa adicionar um bazel_dep
a esse módulo e, em seguida, chamar
o use_extension
integrado
para colocá-la no escopo. Considere o exemplo a seguir, um snippet
um arquivo MODULE.bazel
para usar um "maven" hipotético extensão definida no
Módulo rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Depois de colocar a extensão no escopo, você poderá usar a sintaxe de ponto para
especificar tags para ele. As tags precisam seguir o esquema definido pelo
classes de tag correspondentes (consulte a definição de extensão
abaixo). Veja um exemplo que especifica algumas tags maven.dep
e maven.pom
.
maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")
Se a extensão gerar repositórios que você quer usar no seu módulo, use o
Diretiva use_repo
a ser declarada
para resolvê-los com rapidez. Isso serve para atender à condição de dependências rígidas e evitar o nome do repositório local
conflitos.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
Os repositórios gerados por uma extensão fazem parte da API dela. Portanto, a partir das tags que você
especificado, o "maven" gera uma
"org_junit_junit" e outro chamado "com_google_guava_guava". Com
use_repo
, como alternativa, é possível renomeá-los no escopo do módulo, como em
"guava" aqui.
Definição de extensão
As extensões do módulo são definidas de forma semelhante às regras do repositório, usando a
função module_extension
.
Ambos têm uma função de implementação; mas, embora as regras de repositório tenham uma série de
atributos, as extensões de módulo têm várias
tag_class
es, cada um com um
número de atributos. As classes de tag definem os esquemas das tags usadas por este
. Continuando com nosso exemplo do "maven" hipotético acima:
# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
implementation=_maven_impl,
tag_classes={"dep": maven_dep, "pom": maven_pom},
)
Essas declarações deixam claro que as tags maven.dep
e maven.pom
podem ser
especificado, usando o esquema de atributos definido acima.
A função de implementação é semelhante a uma macro WORKSPACE
, só que
recebe um objeto module_ctx
, que concede
o acesso ao gráfico de dependências e a todas as tags pertinentes. A implementação
precisa chamar as regras de repo para gerar repos:
# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
coords = []
for mod in ctx.modules:
coords += [dep.coord for dep in mod.tags.dep]
output = ctx.execute(["coursier", "resolve", coords]) # hypothetical call
repo_attrs = process_coursier(output)
[maven_single_jar(**attrs) for attrs in repo_attrs]
No exemplo acima, analisamos todos os módulos no gráfico de dependências.
(ctx.modules
), cada uma sendo uma
Objeto bazel_module
com um campo tags
expõe todas as tags maven.*
no módulo. Em seguida, invocamos o utilitário CLI
Coursier para entrar em contato com o Maven e executar a resolução. Por fim, usamos a resolução
resultado para criar vários repositórios, usando o maven_single_jar
hipotético
regra repo.