Módulos do Bazel

Relatar um problema Conferir código-fonte Por noite · 7,4 do Google. 7,3 · 7.2 · 7,1 · 7,0 · 6,5

Um módulo do Bazel é um projeto do Bazel que pode ter várias versões, cada uma 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 npm, um módulo Go ou uma caixa Cargo.

Um módulo precisa ter um arquivo MODULE.bazel na raiz do repositório. Esse é o arquivo manifesto do módulo, declarando seu nome, versão, 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")

Veja a lista completa de diretivas disponíveis em MODULE.bazel.

Para executar a resolução do módulo, o Bazel começa lendo o arquivo MODULE.bazel e, em seguida, solicita repetidamente o MODULE.bazel de um registro do Bazel até descobre todo o gráfico de dependência.

Por padrão, o Bazel seleciona uma versão de cada módulo. usar. 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 diverso, e os projetos usam vários esquemas de controle de versões. O mais conhecido é o SemVer, mas também há projetos importantes que usam esquemas diferentes, como o Abseil, que tem 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 "versão" da versão deve consistir em 3 segmentos: MAJOR.MINOR.PATCH. No Bazel, esse requisito é atenuado para que que qualquer número de segmentos é permitido.
  • No SemVer, cada um dos segmentos na parte "release" precisa ser composto apenas de dígitos. No Bazel, isso é relaxado 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 dos aumentos de versão principal, secundária e de patch não é forçada. No entanto, consulte o nível de compatibilidade para mais detalhes sobre como denotamos 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 da SemVer a e b comparam a < b se e somente se o mesmo for verdadeiro quando forem comparadas como versões de módulo do Bazel.

Seleção da versão

Considere o problema da dependência diamante, um item básico da dependência com controle de versões. gerenciamento de identidade e acesso. 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 Seleção de versão mínima (MVS) introduzido no sistema de módulos Go. O MVS assume 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). É chamado de "mínimo" porque D 1.1 é a versão mais antiga que atende aos nossos requisitos, mesmo que haja uma D 1.2 ou mais recente, não as selecionamos. O uso do MVS cria uma processo de seleção de versão de alta fidelidade e reprodutível.

Versões retiradas

O registro pode declarar determinadas versões como retiradas se elas precisarem ser evitadas, como em caso de vulnerabilidades de segurança. O Bazel gera um erro ao selecionar uma versão removida de um módulo. Para corrigir esse erro, faça upgrade para uma versão mais recente uma versão não puxada ou use o --allow_yanked_versions para permitir explicitamente a versão puxada.

Nível de compatibilidade

No Go, a suposição da MVS sobre compatibilidade com versões anteriores funciona porque ela trata versões incompatíveis com 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 a versão principal no caminho do pacote em Go. Portanto, não há nenhuma conflitos de tempo de compilação ou de vinculação.

No entanto, o Bazel não oferece essas garantias, por isso precisa 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 quando detecta que versões do mesmo módulo com níveis de compatibilidade diferentes existem no gráfico de dependência resolvido.

Modifica

Especifique substituições no arquivo MODULE.bazel para alterar o comportamento da resolução de módulos do Bazel. Somente as substituições do módulo raiz têm efeito. Se um módulo for usado como uma dependência, as substituições dele serão ignoradas.

Cada substituição é especificada para um determinado nome de módulo, o que afeta todos os no gráfico de dependências. Embora apenas as substituições do módulo raiz levem podem ser para dependências transitivas que o módulo raiz não depender 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 em uma versão específica, independentemente de quais versões da dependência são solicitadas no gráfico de dependências.
  • 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 módulo baixado.

Esses atributos são opcionais e podem ser misturados e combinados.

Substituição de várias versões

Uma 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 deve estejam presentes no gráfico de dependências antes da resolução. É preciso que haja 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 atualiza outras versões do módulo para a versão permitida mais próxima no mesmo nível de compatibilidade. Se nenhuma versão superior permitida com a mesma compatibilidade existir, 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 é o arquivo nível:

  • Uma substituição de várias versões que permite 1.3, 1.7 e 2.0 resulta em 1.1 sendo atualizado para 1.3, 1.5 sendo atualizado para 1.7 e outras versões permanecendo iguais.
  • Uma substituição de várias versões que permite 1.5 e 2.0 resulta em erro, assim como O 1.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 e 2.0 resulta em erro, assim como 1.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 sem registro

As substituições sem registro removem completamente um módulo da resolução de versão. Júlio não solicita esses arquivos MODULE.bazel de um registro, mas de o repositório em si.

O Bazel oferece suporte às seguintes substituições sem registro:

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 para ser lido como dados.

Nesse caso, você pode usar a diretiva use_repo_rule para definir diretamente um repositório invocando uma regra de repositório. Este repositório só será visível para o módulo a que definido.

Por trás, 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 dá suporte a um módulo para os dependentes diretos é definido como o nome do módulo, a menos que o atributo repo_name da diretiva bazel_dep diga o contrário. Isso significa que um módulo só pode encontrar as dependências diretas. Isso ajuda a evitar quebras 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 se há várias versões do módulo em todo o gráfico de dependências (consulte multiple_version_override). O formato de nome canônico não é uma API da qual você precisa depender e está sujeito a alterações a qualquer momento. Em vez de fixar o nome canônico, use uma maneira compatível para extraí-lo diretamente do Bazel: * Nos arquivos BUILD e .bzl, use Label.repo_name em uma instância Label criada a partir de uma string de rótulo fornecida pelo nome aparente do repositório, por exemplo, Label("@bazel_skylib").repo_name. * Ao procurar arquivos de execução, use $(rlocationpath ...) ou uma das bibliotecas do runfiles do @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 usando uma ferramenta externa, como um ambiente de desenvolvimento integrado ou uma linguagem servidor, use o comando bazel mod dump_repo_mapping para receber o mapeamento do nomes aparentes a nomes canônicos para um determinado conjunto de repositórios.

As extensões de módulo também podem introduzir repositórios no escopo visível de um módulo.