Arquivo de bloqueio do Bazel

Informar um problema Acessar código-fonte

O recurso de bloqueio de arquivo do Bazel permite a gravação de versões ou dependências específicas de bibliotecas de software ou pacotes exigidos por um projeto. Para isso, ele armazena o resultado da resolução do módulo e da avaliação da extensão. O lockfile promove builds reproduzíveis, garantindo ambientes de desenvolvimento consistentes. Além disso, ela melhora a eficiência do build, permitindo que o Bazel pule as partes do processo de resolução que não são afetadas por mudanças nas dependências do projeto. Além disso, o lockfile melhora a estabilidade evitando atualizações inesperadas ou interrupções em bibliotecas externas, reduzindo o risco de introduzir bugs.

Geração de arquivo de bloqueio

O arquivo de bloqueio é gerado na raiz do espaço de trabalho com o nome MODULE.bazel.lock. Ele é criado ou atualizado durante o processo de build, especificamente após a resolução do módulo e a avaliação da extensão. É importante ressaltar que ele inclui apenas as dependências incluídas na invocação atual do build.

Quando ocorrem alterações no projeto que afetam as dependências, o arquivo de bloqueio é atualizado automaticamente para refletir o novo estado. Isso garante que o arquivo de bloqueio permaneça focado no conjunto específico de dependências necessárias para o build atual, fornecendo uma representação precisa das dependências resolvidas do projeto.

Uso do arquivo de bloqueio

O lockfile pode ser controlado pela flag --lockfile_mode para personalizar o comportamento do Bazel quando o estado do projeto é diferente do lockfile. Os modos disponíveis são:

  • update (padrão): use as informações presentes no arquivo de bloqueio para pular downloads de arquivos de registro conhecidos e evitar a reavaliação das extensões com resultados ainda atualizados. Se faltarem informações, elas serão adicionadas ao arquivo de bloqueio. Nesse modo, o Bazel também evita a atualização de informações mutáveis, como versões puxadas, para dependências que não mudaram.
  • refresh: semelhante a update, mas as informações mutáveis são sempre atualizadas ao mudar para esse modo e aproximadamente a cada hora nesse modo.
  • error: semelhante a update, mas se alguma informação estiver ausente ou desatualizada, o Bazel falhará com um erro. Esse modo nunca altera o arquivo de bloqueio ou executa solicitações de rede durante a resolução. As extensões de módulo que se automarcaram como reproducible ainda podem executar solicitações de rede, mas precisam sempre produzir o mesmo resultado.
  • off: o arquivo de bloqueio não é verificado nem atualizado.

Benefícios do Lockfile

O lockfile oferece vários benefícios e pode ser usado de várias maneiras:

  • Builds reproduzíveis. Ao capturar as versões ou dependências específicas de bibliotecas de software, o arquivo de bloqueio garante que as versões sejam reproduzíveis em diferentes ambientes e ao longo do tempo. Os desenvolvedores podem confiar em resultados consistentes e previsíveis ao criar seus projetos.

  • Resoluções incrementais rápidas. O arquivo de bloqueio permite que o Bazel evite o download de arquivos de registro que já foram usados em um build anterior. Isso melhora significativamente a eficiência do build, especialmente em cenários em que a resolução pode ser demorada.

  • Estabilidade e redução de riscos. O lockfile ajuda a manter a estabilidade, evitando atualizações inesperadas ou alterações interruptivas em bibliotecas externas. Ao bloquear as dependências a versões específicas, o risco de introduzir bugs devido a atualizações incompatíveis ou não testadas é reduzido.

Conteúdo do arquivo de bloqueio

O arquivo de bloqueio contém todas as informações necessárias para determinar se o estado do projeto foi alterado. Também inclui o resultado da criação do projeto no estado atual. O arquivo de bloqueio consiste em duas partes principais:

  1. Hashes de todos os arquivos remotos que são entradas para a resolução do módulo.
  2. Para cada extensão de módulo, o lockfile inclui entradas que o afetam, representadas por bzlTransitiveDigest, usagesDigest e outros campos, bem como a saída da execução dessa extensão, chamada de generatedRepoSpecs.

Confira um exemplo que demonstra a estrutura do arquivo de bloqueio e as explicações de cada seção:

{
  "lockFileVersion": 10,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
    "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
    "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
    "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
    "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
    ...
  },
  "selectedYankedVersions": {
    "foo@2.0": "Yanked for demo purposes"
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

Hashes de arquivo de registro

A seção registryFileHashes contém os hashes de todos os arquivos de registros remotos acessados durante a resolução do módulo. Como o algoritmo de resolução é totalmente determinista quando recebe as mesmas entradas e todas as entradas remotas são criptografadas em hash, isso garante um resultado de resolução totalmente reproduzível e evita a duplicação excessiva de informações remotas no arquivo de bloqueio. Isso também requer gravação quando um registro específico não continha um determinado módulo, mas um registro com precedência menor sim. Consulte a entrada "não encontrado" no exemplo. Essas informações inerentemente mutáveis podem ser atualizadas usando bazel mod deps --lockfile_mode=refresh.

Ele usa os hashes do arquivo de bloqueio para procurar arquivos de registro no cache do repositório antes de fazer o download deles, o que acelera as resoluções subsequentes.

Versões selecionadas

A seção selectedYankedVersions contém as versões puxadas dos módulos que foram selecionados pela resolução do módulo. Como isso geralmente resulta em um erro ao tentar criar, essa seção só fica vazia quando as versões puxadas são explicitamente permitidas por --allow_yanked_versions ou BZLMOD_ALLOW_YANKED_VERSIONS.

Esse campo é necessário porque, em comparação com os arquivos do módulo, as informações da versão extraída são mutáveis e, portanto, não podem ser referenciadas por um hash. Essas informações podem ser atualizadas em bazel mod deps --lockfile_mode=refresh.

Extensões do módulo

A seção moduleExtensions é um mapa que inclui apenas as extensões usadas na invocação atual ou invocadas anteriormente, excluindo aquelas que não são mais utilizadas. Em outras palavras, se uma extensão não estiver mais sendo usada no gráfico de dependência, ela será removida do mapa moduleExtensions.

Se uma extensão for independente do sistema operacional ou do tipo de arquitetura, esta seção apresentará apenas uma única entrada "geral". Caso contrário, várias entradas serão incluídas, nomeadas com base no SO, na arquitetura ou em ambas, com cada correspondente ao resultado da avaliação da extensão nessas especificações.

Cada entrada no mapa de extensões corresponde a uma extensão usada e é identificada pelo arquivo e nome que a contém. O valor correspondente de cada entrada contém as informações relevantes associadas a essa extensão:

  1. O bzlTransitiveDigest é o resumo da implementação da extensão e os arquivos .bzl carregados transitivamente por ela.
  2. O usagesDigest é o resumo dos usos da extensão no gráfico de dependência, que inclui todas as tags.
  3. Campos não especificados que rastreiam outras entradas para a extensão, como conteúdos de arquivos ou diretórios lidos ou variáveis de ambiente que ela usa.
  4. O generatedRepoSpecs codifica os repositórios criados pela extensão com a entrada atual.
  5. O campo opcional moduleExtensionMetadata contém metadados fornecidos pela extensão, como se determinados repositórios criados por ela precisam ser importados via use_repo pelo módulo raiz. Essas informações alimentam o comando bazel mod tidy.

As extensões de módulo podem desativar a inclusão no lockfile definindo os metadados retornados com reproducible = True. Ao fazer isso, eles prometem que sempre criarão os mesmos repositórios quando receberem as mesmas entradas.

Práticas recomendadas

Para maximizar os benefícios do recurso de bloqueio de arquivo, considere as seguintes práticas recomendadas:

  • Atualize regularmente o arquivo de bloqueio para refletir as alterações nas dependências ou na configuração do projeto. Isso garante que os builds subsequentes sejam baseados no conjunto de dependências mais atualizado e preciso. Para bloquear todas as extensões de uma só vez, execute bazel mod deps --lockfile_mode=update.

  • Inclua o arquivo de bloqueio no controle de versões para facilitar a colaboração e garantir que todos os membros da equipe tenham acesso ao mesmo arquivo de bloqueio, promovendo ambientes de desenvolvimento consistentes em todo o projeto.

  • Use bazelisk para executar o Bazel e inclua um arquivo .bazelversion no controle de versões que especifique a versão do Bazel correspondente ao arquivo de bloqueio. Como o próprio Bazel é uma dependência da sua versão, o arquivo de bloqueio é específico dessa versão e é alterado até mesmo entre versões compatíveis com versões anteriores do Bazel. O uso de bazelisk garante que todos os desenvolvedores usem uma versão do Bazel que corresponda ao arquivo de bloqueio.

Seguindo essas práticas recomendadas, você pode utilizar efetivamente o recurso de lockfile no Bazel, levando a fluxos de trabalho de desenvolvimento de software mais eficientes, confiáveis e colaborativos.