Um módulo do Bazel é um projeto do Bazel que pode ter várias versões, cada uma delas publicando metadados sobre outros módulos de que depende. Isso é análogo a conceitos conhecidos em outros sistemas de gerenciamento de dependências, como um artefato do Maven, um pacote do npm, um módulo do Go ou uma caixa do Cargo.
Um módulo precisa ter um arquivo MODULE.bazel na raiz do repositório. Esse arquivo é o
manifesto do módulo, declarando o nome, a versão, a lista de dependências diretas e
outras informações. Confira 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")
Consulte a lista completa de diretivas disponíveis em
MODULE.bazel arquivos.
Para realizar a resolução de módulos, o Bazel começa lendo o arquivo
MODULE.bazel do módulo raiz e, em seguida, solicita repetidamente o arquivo
MODULE.bazel de qualquer dependência de um registro do Bazel até que ele
descubra todo o gráfico de dependência.
Por padrão, o Bazel então seleciona uma versão de cada módulo a ser usada. O Bazel representa cada módulo com um repositório e consulta o registro novamente para saber como definir cada um dos repositórios.
Formato da versão
O Bazel tem um ecossistema diversificado, e os projetos usam vários esquemas de controle de versões. O
mais popular é o SemVer, mas também há
projetos importantes que usam esquemas diferentes, como o
Abseil, cujas
versões são baseadas em datas, por exemplo, 20210324.2).
Por esse motivo, o Bzlmod adota uma versão mais flexível da especificação SemVer. As diferenças incluem:
- O SemVer prescreve que a parte "lançamento" da versão precisa consistir em três
segmentos:
MAJOR.MINOR.PATCH. No Bazel, esse requisito é flexibilizado para que qualquer número de segmentos seja permitido. - No SemVer, cada um dos segmentos na parte "lançamento" precisa ser apenas dígitos. No Bazel, isso é flexibilizado para permitir letras também, e a semântica de comparação corresponde aos "identificadores" na parte "pré-lançamento".
- Além disso, a semântica de aumentos de versão principal, secundária e de patch não é aplicada. No entanto, consulte o nível de compatibilidade para mais detalhes sobre como denotamos a compatibilidade com versões anteriores.
Qualquer versão SemVer válida é uma versão de módulo do Bazel válida. Além disso, duas
versões SemVer a e b comparam a < b se e somente se o mesmo for válido quando
comparadas como versões de módulo do Bazel.
Seleção da versão
Considere o problema de dependência de diamante, um elemento básico no espaço de gerenciamento de dependências com versões. Suponha que você tenha o 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
algoritmo de seleção de versão mínima
(MVS, na sigla em inglês) introduzido no sistema de módulos do Go. O MVS pressupõe que todas as novas
versões de um módulo sejam compatíveis com versões anteriores e, portanto, escolhe a versão mais alta
especificada por qualquer dependente (D 1.1 no nosso exemplo). Ele é chamado de "mínimo"
porque D 1.1 é a versão mais antiga que pode atender aos nossos requisitos.
Mesmo que D 1.2 ou mais recente exista, não a selecionamos. O uso do MVS cria um
processo de seleção de versão de alta fidelidade e reproduzível.
Versões retiradas
O registro pode declarar determinadas versões como retiradas se elas precisarem ser evitadas
(como vulnerabilidades de segurança). O Bazel gera um erro ao selecionar uma
versão retirada de um módulo. Para corrigir esse erro, faça upgrade para uma versão mais recente,
não retirada ou use a
--allow_yanked_versions
flag para permitir explicitamente a versão retirada.
Nível de compatibilidade
No Go, a suposição do MVS sobre a compatibilidade com versões anteriores funciona porque ele trata
versões incompatíveis de um módulo como um módulo separado. Em termos de
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ência resolvido. Isso, por sua vez, é possível pela
codificação da versão principal no caminho do pacote no Go, para que não haja
conflitos de tempo de compilação ou de vinculação.
No entanto, o Bazel não pode fornecer essas garantias. Portanto, ele precisa do número da "versão principal"
para detectar versões incompatíveis com versões anteriores. Esse número é chamado
de nível de compatibilidade e é especificado por cada versão do módulo na
module() diretiva. Com essas informações, o Bazel pode gerar um erro quando ele
detecta que versões do mesmo módulo com níveis de compatibilidade diferentes
existem no gráfico de dependência resolvido.
Modifica
Especifique modificações no arquivo MODULE.bazel para alterar o comportamento da resolução de módulos do Bazel. Somente as modificações do módulo raiz entram em vigor. Se um módulo for
usado como dependência, as modificações serão ignoradas.
Cada modificação é especificada para um determinado nome de módulo, afetando todas as versões dele no gráfico de dependência. Embora apenas as modificações do módulo raiz entrem em vigor, elas podem ser para dependências transitivas de que o módulo raiz não depende diretamente.
Modificação de versão única
O single_version_override
tem várias finalidades:
- Com o atributo
version, é possível fixar uma dependência em uma versão específica, independentemente de quais versões da dependência são solicitadas no gráfico de dependência. - Com o atributo
registry, é possível forçar essa dependência a vir de um registro específico, em vez de seguir o processo normal de seleção de registro. - Com os atributos
patch*, é possível especificar um conjunto de patches a serem aplicados ao módulo transferido por download.
Esses atributos são opcionais e podem ser combinados.
Modificação de várias versões
Uma multiple_version_override
pode ser especificada para permitir que várias versões do mesmo módulo coexistam no
gráfico de dependência resolvido.
É possível especificar uma lista explícita de versões permitidas para o módulo, que precisam estar presentes no gráfico de dependência antes da resolução. É necessário que exista alguma dependência transitiva dependendo de cada versão permitida. Após a resolução, apenas as versões permitidas do módulo permanecem, enquanto o Bazel faz upgrade de outras versões do módulo para a versão permitida mais próxima no mesmo nível de compatibilidade. Se não houver uma versão permitida mais alta no mesmo nível de compatibilidade, o Bazel vai gerar um erro.
Por exemplo, se as versões 1.1, 1.3, 1.5, 1.7, e 2.0 existirem no
gráfico de dependência antes da resolução e a versão principal for o nível de compatibilidade:
- Uma modificação de várias versões que permite
1.3,1.7e2.0resulta no upgrade de1.1para1.3,1.5para1.7e outras versões permanecem as mesmas. - Uma modificação de várias versões que permite
1.5e2.0resulta em um erro, já que1.7não tem uma versão mais alta no mesmo nível de compatibilidade para fazer upgrade. - Uma modificação de várias versões que permite
1.9e2.0resulta em um erro, já que1.9não está presente no gráfico de dependência antes da resolução.
Além disso, os usuários também podem modificar o registro usando o registry
atributo, de maneira semelhante às modificações de versão única.
Modificações não registradas
As modificações não registradas removem completamente um módulo da resolução de versão. O Bazel
não solicita esses arquivos MODULE.bazel de um registro, mas do
próprio repositório.
O Bazel oferece suporte às seguintes modificações não registradas:
Definir repositórios que não representam módulos do Bazel
Com bazel_dep, é possível definir repositórios que representam outros módulos do Bazel.
Às vezes, é necessário definir um repositório que não represente um módulo
do Bazel, por exemplo, um que contenha um arquivo JSON simples a ser lido como dados.
Nesse caso, é possível usar a use_repo_rule
diretiva para definir diretamente um repositório
invocando uma regra de repositório. Esse repositório só ficará visível para o módulo em que está
definido.
Nos bastidores, isso é implementado usando o mesmo mecanismo das extensões de módulo, que permite definir repositórios com mais flexibilidade.
Nomes de repositório e dependências estritas
O nome aparente de um repositório que faz backup de um
módulo para seus dependentes diretos é o nome do módulo, a menos que o
repo_name atributo da bazel_dep
diretiva diga o contrário. Isso significa que um módulo só pode encontrar as dependências diretas. Isso ajuda a evitar interrupções acidentais devido a mudanças em
dependências transitivas.
O nome canônico de um repositório que faz backup de um
módulo é module_name~version (por exemplo, bazel_skylib~1.0.3) ou module_name~ (por exemplo, bazel_features~), dependendo de haver
várias versões do módulo em todo o gráfico de dependência (consulte
multiple_version_override).
Observe que o formato de nome canônico não é uma API de que você deve depender e
está sujeito a mudanças a qualquer momento. Em vez de codificar o nome canônico,
use uma maneira compatível de recebê-lo diretamente do Bazel:
* Em arquivos BUILD e .bzl, use
Label.repo_name em uma instância Label
construída a partir de uma string de rótulo fornecida pelo nome aparente do repositório, por exemplo,
Label("@bazel_skylib").repo_name.
* Ao pesquisar arquivos de execução, use
$(rlocationpath ...)
ou uma das bibliotecas de arquivos de execução em
@bazel_tools//tools/{bash,cpp,java}/runfiles ou, para um conjunto de regras rules_foo,
em @rules_foo//foo/runfiles.
* Ao interagir com o Bazel de uma ferramenta externa, como um ambiente de desenvolvimento integrado ou um servidor de linguagem
use o bazel mod dump_repo_mapping comando para receber o mapeamento de
nomes aparentes para nomes canônicos de um determinado conjunto de repositórios.
As extensões de módulo também podem introduzir outros repositórios no escopo visível de um módulo.