Módulos do Bazel

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 dos quais 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. 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")

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é descobrir 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 Bazel 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.

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.

Por fim, para saber mais sobre o controle de versões de módulos, consulte as MODULE.bazel Perguntas frequentes.

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 Bazel usa o algoritmo de seleção de versão mínima (MVS, na sigla em inglês) introduzido no sistema de módulos 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 (por exemplo, para 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.

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 das quais 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 sejam 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.

Se houver várias versões do mesmo módulo restantes no gráfico de dependência, o Bazel vai escolher a versão mais alta permitida mais próxima para cada dependente.

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:

  • Uma modificação de várias versões que permite 1.3, 1.7 e 2.0 resulta no upgrade de 1.1 para 1.3, 1.5 para 1.7 e outras versões permanecem as mesmas.
  • Uma modificação de várias versões que permite 1.9 e 2.0 resulta em um erro, já que 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 modificar o registro usando o atributo registry, 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 um valor de versão no arquivo de origem MODULE.bazel pode ter desvantagens quando o módulo é modificado com uma modificação não registrada. Para saber mais sobre isso, consulte as MODULE.bazel Perguntas frequentes.

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 diretiva bazel_dep 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 da qual 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 comando bazel mod dump_repo_mapping 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.