Um módulo do Bazel é um projeto que pode ter várias versões, e cada uma delas publica 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 npm, um módulo Go ou uma caixa do Cargo.
Um módulo precisa ter um arquivo MODULE.bazel
na raiz do repositório, ao lado do
arquivo WORKSPACE
. Esse arquivo é o manifesto do módulo, declarando o nome,
a versão, a lista de dependências diretas e outras informações. Para 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")
Para executar a resolução do módulo, o Bazel começa lendo o arquivo
MODULE.bazel
do módulo raiz e solicita repetidamente o arquivo
MODULE.bazel
da dependência de um registro do Bazel até
descobrir todo o gráfico de dependência.
Por padrão, o Bazel seleciona uma versão de cada módulo a ser usada. Ele representa cada módulo com um repositório e consulta o registro novamente para aprender a definir cada um dos repositórios.
Formato da versão
O Bazel tem um ecossistema diverso, e os projetos usam vários esquemas de controle de versões. O mais conhecido, de longe, é o SemVer, mas também há projetos proeminentes que usam esquemas diferentes, como o Abseil, com versões baseadas em data, 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 determina que a parte de "lançamento" da versão precisa consistir em três
segmentos:
MAJOR.MINOR.PATCH
. No Bazel, esse requisito é reduzido para que qualquer número de segmentos seja permitido. - No SemVer, cada um dos segmentos na parte "lançamento" precisa ter apenas dígitos. No Bazel, isso é diminuído 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ões principais, secundárias e de patch não são aplicadas. No entanto, consulte o nível de compatibilidade para conferir 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, duas versões do SemVer, a
e b
, comparam a < b
somente se as mesmas forem aplicadas quando forem comparadas às versões do módulo do Bazel.
Escolher a versão
Considere o problema de dependência diamante, um item básico no espaço de gerenciamento de dependências com controle de versões. Suponha que você tenha o gráfico de dependências:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
Qual versão do D
deve ser usada? Para resolver essa questão, o Bzlmod usa o algoritmo de seleção mínima de versão (MVS, na sigla em inglês) introduzido no sistema de módulos Go. O MVS presume que todas as novas
versões de um módulo são compatíveis com versões anteriores e, portanto, escolhe a versão mais recente
especificada por qualquer dependente (D 1.1
no nosso exemplo). Ela é chamada de "mínima",
porque D 1.1
é a versão mais antiga que pode atender aos nossos requisitos.
mesmo que haja uma D 1.2
ou mais recente, não a selecionaremos. O uso do MVS cria um
processo de seleção de versão de alta fidelidade e reprodutível.
Versões Yangue
O registro pode declarar determinadas versões como ianizadas se elas precisarem ser evitadas
(por exemplo, para vulnerabilidades de segurança). O Bazel gera um erro ao selecionar uma
versão puxada de um módulo. Para corrigir esse erro, faça upgrade para uma versão mais recente
e não puxada ou use a sinalização
--allow_yanked_versions
para permitir explicitamente a versão removida.
Nível de compatibilidade
No Go, a suposição do MVS sobre compatibilidade com versões anteriores funciona porque ele trata
as versões incompatíveis com versões anteriores de um módulo como um módulo separado. Em termos do
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 resolvido. Isso é possível, por sua vez, codificando a versão principal no caminho do pacote em Go. Portanto, não há conflitos de tempo de compilação ou de vinculação.
No entanto, o Bazel não oferece essas garantias, então 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 de módulo na
diretiva module()
. Com essas informações, o Bazel pode gerar um erro quando
detecta que existem versões do mesmo módulo com diferentes níveis de compatibilidade
no gráfico de dependência resolvido.
Modifica
Especifique substituições no arquivo MODULE.bazel
para mudar o comportamento da resolução do módulo
do Bazel. Somente as substituições do módulo raiz entram em vigor. Se um módulo for
usado como dependência, as substituições dele serão ignoradas.
Cada substituição é especificada para um determinado nome de módulo, afetando todas as versões dele no gráfico de dependência. Embora apenas as substituições do módulo raiz entrem em vigor, elas podem ser para dependências transitivas das quais o módulo raiz não depende diretamente.
Substituição de versão única
O single_version_override
tem várias finalidades:
- Com o atributo
version
, é possível fixar uma dependência a uma versão específica, independente 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 misturados e combinados.
Substituição de várias versões
Um multiple_version_override
pode ser especificado para permitir que várias versões do mesmo módulo coexistam no
gráfico de dependências 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ências 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 mais recente permitida mais próxima no mesmo nível de compatibilidade. Se não houver uma versão mais recente com o 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 substituição de várias versões que permite
1.3
,1.7
e2.0
resulta no upgrade de1.1
para1.3
, de1.5
para1.7
e de outras versões. - Uma substituição de várias versões que permite
1.5
e2.0
resulta em erro, já que1.7
não tem uma versão mais recente com o mesmo nível de compatibilidade para fazer upgrade. - Uma substituição de várias versões que permite
1.9
e2.0
resulta em erro, já que1.9
não está presente no gráfico de dependência antes da resolução.
Além disso, os usuários também podem substituir o registro usando o atributo registry
, de forma semelhante às substituições de versão única.
Substituições que não são do registro
As substituições que não são do registro removem completamente um módulo da resolução de versão. Ele
não solicita esses arquivos MODULE.bazel
de um registro, mas do
próprio repo.
O Bazel oferece suporte às seguintes substituições que não são do registro:
Nomes de repositório e dependências estritas
O nome canônico de um repositório que apoia um
módulo é module_name~version
(por exemplo, bazel_skylib~1.0.3
). Para módulos com uma
substituição que não seja do registro, substitua a parte version
pela string override
. O formato de nome canônico não é uma API
de que você precisa depender e está sujeito a mudanças a qualquer momento.
O nome aparente de um repositório que dá suporte a um
módulo para dependentes diretos é o padrão do nome do módulo, a menos que o
atributo repo_name
da diretiva
bazel_dep
informe o contrário. Isso significa que um módulo só pode encontrar as dependências
diretas dele. Isso ajuda a evitar interrupções acidentais devido a mudanças nas
dependências transitivas.
As extensões de módulo também podem introduzir outros repositórios no escopo visível de um módulo.