Um módulo do Bazel é um projeto do Bazel que pode ter várias versões. Cada uma delas publica metadados sobre outros módulos dos quais depende. Isso é análogo a conceitos conhecidos em outros sistemas de gerenciamento de dependências, como um artefato do Maven, um pacote de npm, um módulo do Go ou uma caixa de carga.
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. Veja 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")
Veja a lista completa de diretivas disponíveis em arquivos MODULE.bazel
.
Para realizar a resolução do módulo, 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é descobrir todo o gráfico de dependência.
Por padrão, o Bazel seleciona uma versão de cada módulo para usar. O Bazel representa cada módulo com um repositório e consulta o registro novamente para saber como definir cada um deles.
Formato da versão
O Bazel tem um ecossistema diversificado, e os projetos usam vários esquemas de controle de versão. A mais conhecida é a SemVer, mas também há projetos em destaque que usam esquemas diferentes, como o Abseil, cujas versões se baseiam em data, por exemplo, 20210324.2
.
Por isso, o Bzlmod adota uma versão mais flexível da especificação SemVer. As diferenças incluem:
- A SemVer determina que a parte "release" da versão precisa consistir em três segmentos:
MAJOR.MINOR.PATCH
. No Bazel, esse requisito é diminuído para que qualquer número de segmentos seja permitido. - No SemVer, cada um dos segmentos na parte de "lançamento" precisa ter apenas dígitos. No Bazel, isso também é liberado para permitir letras, e a semântica de comparação corresponde aos "identificadores" na parte de "pré-lançamento".
- Além disso, a semântica dos aumentos de versões principais, secundárias e de patch não é aplicada. No entanto, consulte o nível de compatibilidade para detalhes sobre como denotamos 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 SemVer a
e b
compararão a < b
se e somente se as mesmas forem mantidas quando forem comparadas como versões de módulo do Bazel.
Escolher a versão
Considere o problema de dependência losango, um elemento básico no espaço de gerenciamento de dependência com controle de versão. 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 do 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 do módulo Go. O MVS pressupõe 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 alta especificada por qualquer dependência (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 D 1.2
ou mais recente exista, não as selecionamos. O uso do MVS cria um processo de seleção de versão de alta fidelidade e reprodutível.
Versões afetadas
O registro pode declarar certas versões como yanked se elas devem ser evitadas
(por exemplo, para vulnerabilidades de segurança). O Bazel gera um erro ao selecionar a versão de um módulo. Para corrigir esse erro, faça upgrade para uma versão mais recente
sem problemas ou use a sinalização
--allow_yanked_versions
para permitir explicitamente o uso da versão com problemas.
Nível de compatibilidade
No Go, a suposição do MVS sobre compatibilidade com versões anteriores funciona porque trata versões incompatíveis de versões anteriores 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ências resolvido. Isso, por sua vez, é possível codificando a 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. Por isso, 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
diretiva module()
. Com essas informações, o Bazel pode gerar um erro ao detectar que existem versões do mesmo módulo com diferentes níveis de compatibilidade no gráfico de dependências resolvidas.
Modifica
Especifique substituições no arquivo MODULE.bazel
para alterar o comportamento da resolução do módulo do Bazel. Somente as modificações do módulo raiz são aplicadas. Se um módulo for usado como dependência, as modificações dele serão ignoradas.
Cada modificação é especificada para um determinado nome de módulo, afetando todas as versões no gráfico de dependência. Embora somente 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 das versões da dependência solicitadas no gráfico de dependência. - Com o atributo
registry
, é possível forçar essa dependência a partir 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 para aplicar ao módulo transferido por download.
Todos esses atributos são opcionais e podem ser combinados.
Modificaçã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.
Você pode 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 existam algumas dependências transitivas dependendo de cada versão permitida. Após a resolução, apenas as versões permitidas do módulo serão mantidas, enquanto o Bazel fará o upgrade de outras versões do módulo para a versão superior mais próxima permitida no mesmo nível de compatibilidade. Se não houver uma versão mais recente com o mesmo nível de compatibilidade, o Bazel lançará 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 que
1.3
,1.7
e2.0
resultem no upgrade de1.1
para1.3
, upgrade de1.5
para1.7
e outras versões iguais. - Uma modificação de várias versões que permite
1.5
e2.0
resulta em um erro, já que1.7
não tem uma versão superior no mesmo nível de compatibilidade para o upgrade. - Uma modificação de várias versões que permite
1.9
e2.0
resulta em um 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 modificar o registro usando o atributo registry
, de maneira semelhante às substituições de versão única.
Substituições não relacionadas ao registro
As substituições que não são de registro removem completamente um módulo da resolução da versão. O Bazel não solicita esses arquivos MODULE.bazel
de um registro, mas do próprio repo.
O Bazel é compatível com as seguintes substituições não relacionadas ao registro:
Nomes de repositório e dependências restritas
O nome canônico de um repositório que oferece suporte a 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 e está sujeito a alterações a qualquer momento.
O nome aparente de um repo que apoia um
módulo para os dependentes diretos usa o nome do módulo como padrão, a menos que o
atributo repo_name
da diretiva bazel_dep
diga o contrário. Isso significa que um módulo pode apenas encontrar as dependências
diretas. Isso ajuda a evitar interrupções acidentais devido a alterações em dependências transitivas.
As extensões de módulo também podem introduzir mais repositórios no escopo visível de um módulo.