O recurso de arquivo de bloqueio no Bazel permite gravar 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 arquivo de bloqueio promove builds reproduzíveis, garantindo ambientes de desenvolvimento consistentes. Além disso, ele aumenta 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 ao impedir atualizações inesperadas ou mudanças incompatíveis 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 que estão na invocação atual do
build.
Quando ocorrem mudanças no projeto que afetam as dependências dele, 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 arquivo de bloqueio pode ser controlado pela flag
--lockfile_mode
para
personalizar o comportamento do Bazel quando o estado do projeto é diferente do
arquivo de bloqueio. 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 de extensões cujos resultados ainda estão atualizados. Se alguma informação estiver faltando, ela será adicionada ao arquivo de bloqueio. Nesse modo, o Bazel também evita atualizar informações mutáveis, como versões descartadas, para dependências que não foram alteradas.refresh
: igual aupdate
, mas as informações mutáveis são sempre atualizadas ao mudar para esse modo e aproximadamente a cada hora enquanto estiver nele.error
: semelhante aupdate
, mas se alguma informação estiver faltando ou desatualizada, o Bazel vai falhar com um erro. Esse modo nunca muda o arquivo de bloqueio nem faz solicitações de rede durante a resolução. As extensões de módulo que se marcaram comoreproducible
ainda podem fazer solicitações de rede, mas sempre devem produzir o mesmo resultado.off
: o arquivo de bloqueio não é verificado nem atualizado.
Benefícios do arquivo de bloqueio
O arquivo de bloqueio 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 os builds sejam reproduzíveis em diferentes ambientes e ao longo do tempo. Os desenvolvedores podem contar com resultados consistentes e previsíveis ao criar projetos.
Resoluções incrementais rápidas. O lockfile permite que o Bazel evite fazer 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 arquivo de bloqueio ajuda a manter a estabilidade, evitando atualizações inesperadas ou mudanças interruptivas em bibliotecas externas. Ao bloquear as dependências em versões específicas, o risco de introduzir bugs
devido a atualizações incompatíveis ou não testadas.
Arquivo de bloqueio oculto
O Bazel também mantém outro arquivo de bloqueio em
"$(bazel info output_base)"/MODULE.bazel.lock
. O formato e o conteúdo deste
arquivo de bloqueio não são especificados explicitamente. Ele é usado apenas como uma otimização de desempenho. Embora ele possa ser excluído junto com a base de saída usando
bazel clean --expunge
, qualquer necessidade de fazer isso é um bug no próprio Bazel ou em uma
extensão de módulo.
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 mudou. Ele também inclui o resultado da criação do projeto no estado atual. O arquivo de bloqueio consiste em duas partes principais:
- Hashes de todos os arquivos remotos que são entradas para a resolução de módulos.
- Para cada extensão de módulo, o arquivo de bloqueio inclui entradas que a afetam, representadas por
bzlTransitiveDigest
,usagesDigest
e outros campos, bem como a saída da execução dessa extensão, chamada degeneratedRepoSpecs
.
Confira um exemplo que demonstra a estrutura do arquivo de bloqueio, além de explicações para 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 arquivos 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 hashizadas, isso garante um resultado de resolução totalmente reproduzível, evitando a duplicação excessiva de informações remotas no arquivo de bloqueio. Isso também exige o registro de quando um registro específico não continha um determinado módulo, mas um registro com precedência menor continha. Consulte a entrada "não encontrado" no exemplo. Essas informações inerentemente mutáveis podem ser atualizadas via
bazel mod deps --lockfile_mode=refresh
.
O Bazel usa os hashes do arquivo de bloqueio para pesquisar arquivos de registro no cache do repositório antes de fazer o download deles, o que acelera as resoluções subsequentes.
Versões removidas selecionadas
A seção selectedYankedVersions
contém as versões removidas dos módulos
que foram selecionados pela resolução de módulos. Como isso geralmente resulta em um erro
ao tentar criar, essa seção só não fica vazia quando as versões removidas são
explicitamente permitidas por --allow_yanked_versions
ou
BZLMOD_ALLOW_YANKED_VERSIONS
.
Esse campo é necessário porque, em comparação com arquivos de módulo, as informações de versão removidas
são inerentemente 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 de módulo
A seção moduleExtensions
é um mapa que inclui apenas as extensões usadas
na invocação atual ou invocadas anteriormente, excluindo as 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 terá apenas uma entrada "geral". Caso contrário, várias entradas serão incluídas, nomeadas de acordo com o SO, a arquitetura ou ambos, e cada uma corresponderá ao resultado da avaliação da extensão nessas especificidades.
Cada entrada no mapa de extensão 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:
- O
bzlTransitiveDigest
é o resumo da implementação da extensão e os arquivos .bzl carregados de forma transitiva por ela. - O
usagesDigest
é o resumo dos usos da extensão no gráfico de dependências, que inclui todas as tags. - Outros campos não especificados que rastreiam outras entradas da extensão, como conteúdo de arquivos ou diretórios que ela lê ou variáveis de ambiente que ela usa.
- O
generatedRepoSpecs
codifica os repositórios criados pela extensão com a entrada atual. - O campo opcional
moduleExtensionMetadata
contém metadados fornecidos pela extensão, como se determinados repositórios criados por ela precisam ser importados viause_repo
pelo módulo raiz. Essas informações alimentam o comandobazel mod tidy
.
As extensões de módulo podem recusar a inclusão no arquivo de bloqueio definindo os
metadados retornados com reproducible = True
. Ao fazer isso, eles prometem que sempre vão criar os mesmos repositórios quando receberem as mesmas entradas.
Práticas recomendadas
Para maximizar os benefícios do recurso de arquivo de bloqueio, considere as seguintes práticas recomendadas:
Atualize regularmente o arquivo de bloqueio para refletir mudanças 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ão que especifica a versão do Bazel correspondente ao arquivo de bloqueio. Como o Bazel é uma dependência da compilação, o arquivo de bloqueio é específico da versão do Bazel e muda mesmo entre versões do Bazel compatíveis com versões anteriores. Usarbazelisk
garante que todos os desenvolvedores estejam usando uma versão do Bazel que corresponda ao arquivo de bloqueio.
Ao seguir essas práticas recomendadas, você pode usar com eficiência o recurso de arquivo de bloqueio no Bazel, resultando em fluxos de trabalho de desenvolvimento de software mais eficientes, confiáveis e colaborativos.
Conflitos de mesclagem
O formato do arquivo de bloqueio foi projetado para minimizar conflitos de mesclagem, mas eles ainda podem ocorrer.
Resolução automática
O Bazel oferece um driver de mesclagem do Git personalizado para ajudar a resolver esses conflitos automaticamente.
Configure o driver adicionando esta linha a um arquivo .gitattributes
na raiz do seu repositório git:
# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge
Em seguida, cada desenvolvedor que quiser usar o driver precisa registrá-lo uma vez seguindo estas etapas:
- Instale o jq (1.5 ou mais recente).
- Execute os comandos a seguir:
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"
Resolução manual
Conflitos de mesclagem simples nos campos registryFileHashes
e selectedYankedVersions
podem ser resolvidos com segurança mantendo todas as entradas dos dois lados do conflito.
Outros tipos de conflitos de mesclagem não devem ser resolvidos manualmente. Em vez disso:
- Restaure o estado anterior do arquivo de bloqueio
usando
git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock
. - Resolva os conflitos no arquivo
MODULE.bazel
. - Execute
bazel mod deps
para atualizar o arquivo de bloqueio.