Esta página responde a algumas perguntas frequentes sobre dependências externas no Bazel.
MODULE.bazel
Como faço o controle de versão de um módulo do Bazel?
Definir version com a module diretiva no arquivo de origem
MODULE.bazel pode ter várias desvantagens e efeitos colaterais indesejados se não for
gerenciado com cuidado:
Duplicação: o lançamento de uma nova versão de um módulo geralmente envolve o incremento da versão em
MODULE.bazele a marcação da versão, duas etapas separadas que podem ficar dessincronizadas. Embora a automação possa reduzir esse risco, é mais simples e seguro evitá-lo completamente.Inconsistência: os usuários que substituem um módulo por um commit específico usando uma substituição não registrada vão encontrar uma versão incorreta. Por exemplo, se o
MODULE.bazelno arquivo de origem definirversion = "0.3.0", mas commits adicionais forem feitos após esse lançamento, um usuário que substituir com um desses commits ainda verá0.3.0. Na realidade, a versão precisa refletir que está à frente do lançamento, por exemplo,0.3.1-rc1.Problemas de substituição não registrada: o uso de valores de marcador pode causar problemas quando os usuários substituem um módulo por uma substituição não registrada. Por exemplo,
0.0.0não é classificado como a versão mais alta, que geralmente é o comportamento esperado pelos usuários ao fazer uma substituição não registrada.
Portanto, é melhor evitar definir a versão no arquivo de origem MODULE.bazel. Em vez disso, defina-o no MODULE.bazel armazenado no registro
(por exemplo, o Bazel Central Registry), que é a origem real da verdade para
a versão do módulo durante a resolução de dependência externa do Bazel (consulte Registros
do Bazel).
Isso geralmente é automatizado. Por exemplo, o rules-template repositório de regras de exemplo
usa uma ação do GitHub bazel-contrib/publish-to-bcr publish.yaml para
publicar a versão no BCR. A ação gera um patch para a origem
arquivo MODULE.bazel com a versão de lançamento. Esse patch é armazenado no registro e aplicado quando o módulo é buscado durante a resolução de dependência externa do Bazel.
Dessa forma, a versão nos lançamentos no registro será definida corretamente como
a versão lançada e, portanto, bazel_dep, single_version_override e
multiple_version_override vão funcionar conforme o esperado, evitando possíveis
problemas ao fazer uma substituição não registrada, porque a versão no arquivo de
origem será o valor padrão (''), que sempre será processado
corretamente (é o valor da versão padrão, afinal) e se comportará conforme o
esperado ao classificar (a string vazia é tratada como a versão mais alta).
Quando devo aumentar o nível de compatibilidade?
O compatibility_level de um módulo do Bazel
precisa ser incrementado no mesmo commit que introduz uma mudança incompatível com versões anteriores ("quebra").
No entanto, o Bazel pode gerar um erro se detectar que versões do mesmo módulo com níveis de compatibilidade diferentes existem no gráfico de dependência resolvido. Isso pode acontecer quando, por exemplo, dois módulos dependem de versões de um terceiro módulo com níveis de compatibilidade diferentes.
Portanto, aumentar compatibility_level com muita frequência pode ser muito prejudicial e não é recomendado. Para evitar essa situação, o compatibility_level precisa ser incrementado somente quando a mudança interruptiva afeta a maioria dos casos de uso e não é fácil de migrar e/ou contornar.
Por que o MODULE.bazel não oferece suporte a loads?
Durante a resolução de dependência, o arquivo MODULE.bazel de todas as dependências externas referenciadas é buscado nos registros. Nessa fase, os arquivos de origem das dependências ainda não foram buscados. Portanto, se o arquivo MODULE.bazel loads outro arquivo, não há como o Bazel buscar esse arquivo sem buscar o arquivo de origem inteiro. O arquivo MODULE.bazel em si é especial, porque é hospedado diretamente no registro.
Há alguns casos de uso em que as pessoas que pedem loads no MODULE.bazel geralmente estão interessadas, e eles podem ser resolvidos sem loads:
- Garantir que a versão listada no MODULE.bazel seja consistente com os metadados de build armazenados em outro lugar, por exemplo, em um arquivo .bzl: isso pode ser feito usando o método
native.module_versionem um arquivo .bzl carregado de um arquivo BUILD. - Dividir um arquivo MODULE.bazel muito grande em seções gerenciáveis,
principalmente para monorepos: o módulo raiz pode usar a
includediretiva para dividir o arquivo MODULE.bazel em vários segmentos. Pelo mesmo motivo que não permitimosloads em arquivos MODULE.bazel,includenão pode ser usado em módulos não raiz. - Os usuários do sistema WORKSPACE antigo podem se lembrar de declarar um repositório e, em seguida,
loading imediatamente desse repositório para realizar uma lógica complexa. Essa capacidade foi substituída por extensões de módulo.
Posso especificar um intervalo SemVer para um bazel_dep?
Não. Alguns outros gerenciadores de pacotes, como npm e Cargo oferecem suporte a intervalos de versão (implícita ou explicitamente), e isso geralmente exige um solucionador de restrições (tornando a saída mais difícil de prever para os usuários) e torna a resolução de versão não reproduzível sem um arquivo de bloqueio.
Em vez disso, o Bazel usa a seleção de versão mínima como o Go, que, em contraste, facilita a previsão da saída e garante a reprodução. Essa é uma compensação que corresponde às metas de design do Bazel.
Além disso, as versões do módulo do Bazel são um superconjunto do SemVer. Portanto, o que faz sentido em um ambiente SemVer estrito nem sempre é transferido para as versões do módulo do Bazel.
Posso receber automaticamente a versão mais recente de um bazel_dep?
Alguns usuários ocasionalmente pedem a capacidade de especificar bazel_dep(name = "foo",
version = "latest") para receber automaticamente a versão mais recente de uma dependência. Isso é
semelhante a pergunta sobre intervalos SemVer, e a resposta também é
não.
A solução recomendada aqui é que a automação cuide disso. Por exemplo, o Renovate oferece suporte a módulos do Bazel.
Às vezes, os usuários que fazem essa pergunta estão procurando uma maneira de iterar rapidamente durante o desenvolvimento local. Isso pode ser feito usando um
local_path_override.
Por que todos esses use_repos?
Os usos de extensão de módulo em arquivos MODULE.bazel às vezes vêm com uma diretiva use_repo grande. Por exemplo, um uso típico da
go_deps extensão de gazelle pode ser assim:
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"com_github_gogo_protobuf",
"com_github_golang_mock",
"com_github_golang_protobuf",
"org_golang_x_net",
... # potentially dozens of lines...
)
A diretiva use_repo longa pode parecer redundante, já que as informações já estão no arquivo go.mod referenciado.
O motivo pelo qual o Bazel precisa dessa diretiva use_repo é que ele executa extensões de módulo de forma lenta. Ou seja, uma extensão de módulo só é executada se o resultado for observado. Como a "saída" de uma extensão de módulo são definições de repo, isso significa que só executamos uma extensão de módulo se um repo definido por ela for solicitado (por exemplo, se o destino @org_golang_x_net//:foo for criado, no exemplo acima). No entanto, não sabemos quais repositórios uma extensão de módulo definiria até que a executemos. É aqui que a diretiva use_repo entra em cena. O usuário pode informar ao Bazel quais repositórios ele espera que a extensão gere, e o Bazel só executará a extensão quando esses repositórios específicos forem usados.
Para ajudar a manter essa diretiva use_repo, uma extensão de módulo pode retornar
um extension_metadata
objeto da função de implementação. O usuário pode executar o comando bazel mod tidy para atualizar as diretivas use_repo dessas extensões de módulo.
Migração do Bzlmod
Qual é avaliado primeiro, MODULE.bazel ou WORKSPACE?
Quando --enable_bzlmod e --enable_workspace estão definidos, é natural se perguntar qual sistema é consultado primeiro. A resposta curta é que o MODULE.bazel (Bzlmod) é avaliado primeiro.
A resposta longa é que "qual é avaliado primeiro" não é a pergunta certa a ser
feita. Em vez disso, a pergunta certa é: no contexto do repositório com
o nome canônico @@foo, o que o nome do repositório
aparente @bar resolve? Como alternativa, qual é o mapeamento do repositório de @@base?
Os rótulos com nomes de repositório aparentes (um único @ inicial) podem se referir a coisas diferentes com base no contexto em que são resolvidos. Quando você vê um rótulo @bar//:baz e se pergunta para o que ele realmente aponta, primeiro é necessário descobrir qual é o repositório de contexto. Por exemplo, se o rótulo estiver em um arquivo BUILD localizado no repositório @@foo, o repositório de contexto será @@foo.
Em seguida, dependendo do repositório de contexto, a tabela "Visibilidade do repositório" no guia de migração pode ser usada para descobrir a qual repositório um nome aparente realmente resolve.
- Se o repositório de contexto for o repositório principal (
@@):- Se
barfor um nome de repositório aparente introduzido pelo arquivo MODULE.bazel do módulo raiz (por qualquer um debazel_dep,use_repo,module,use_repo_rule), então@barserá resolvido para o que esse arquivo MODULE.bazel afirma. - Caso contrário, se
barfor um repositório definido no WORKSPACE (o que significa que o nome canônico é@@bar), então@barserá resolvido para@@bar. - Caso contrário,
@barserá resolvido para algo como@@[unknown repo 'bar' requested from @@], e isso acabará resultando em um erro.
- Se
- Se o repositório de contexto for um repositório do mundo Bzlmod (ou seja, ele corresponde a um módulo do Bazel não raiz ou é gerado por uma extensão de módulo), ele só vai ver outros repositórios do mundo Bzlmod e nenhum repositório do mundo WORKSPACE.
- Isso inclui todos os repositórios introduzidos em uma extensão de módulo semelhante a
non_module_depsno módulo raiz ou instânciasuse_repo_ruleno módulo raiz.
- Isso inclui todos os repositórios introduzidos em uma extensão de módulo semelhante a
- Se o repositório de contexto for definido no WORKSPACE:
- Primeiro, verifique se a definição do repositório de contexto tem o atributo mágico
repo_mapping. Se for o caso, passe pelo mapeamento primeiro (portanto, para um repositório definido comrepo_mapping = {"@bar": "@baz"}, vamos analisar at@bazabaixo). - Se
barfor um nome de repositório aparente introduzido pelo arquivo MODULE.bazel do módulo raiz, então@barserá resolvido para o que esse arquivo MODULE.bazel afirma. Isso é o mesmo que o item 1 no caso do repositório principal. - Caso contrário,
@barserá resolvido para@@bar. Isso provavelmente vai apontar para um repositóriobardefinido no WORKSPACE. Se esse repositório não estiver definido, o Bazel vai gerar um erro.
- Primeiro, verifique se a definição do repositório de contexto tem o atributo mágico
Para uma versão mais concisa:
- Os repositórios do mundo Bzlmod (exceto o repositório principal) só vão ver repositórios do mundo Bzlmod.
- Os repositórios do mundo WORKSPACE (incluindo o repositório principal) vão primeiro ver o que o módulo raiz no mundo Bzlmod define e, em seguida, voltar para ver os repositórios do mundo WORKSPACE.
Os rótulos na linha de comando do Bazel (incluindo flags do Starlark, valores de flag com tipo de rótulo e padrões de destino de build/teste) são tratados como tendo o repositório principal como o repositório de contexto.
Outro
Como preparo e executo um build off-line?
Use o comando bazel fetch para buscar repositórios. Você pode usar a flag --repo (como bazel fetch --repo @foo) para buscar apenas o repo @foo (resolvido no contexto do repo principal, consulte a pergunta acima) ou usar um padrão de destino (como bazel fetch @foo//:bar) para buscar todas as dependências transitivas de @foo//:bar (isso é equivalente a bazel build --nobuild @foo//:bar).
Para garantir que nenhuma busca ocorra durante um build, use --nofetch. Mais precisamente, isso faz com que qualquer tentativa de executar uma regra de repositório não local falhe.
Se você quiser buscar repositórios e modificá-los para testar localmente, use
o bazel vendor comando.
Como uso proxies HTTP?
O Bazel respeita as variáveis de ambiente http_proxy e HTTPS_PROXY comumente
aceitas por outros programas, como
o curl.
Como faço para que o Bazel prefira IPv6 em configurações de pilha dupla IPv4/IPv6?
Em máquinas somente IPv6, o Bazel pode fazer o download de dependências sem alterações. No entanto, em máquinas de pilha dupla IPv4/IPv6, o Bazel segue a mesma convenção do Java, preferindo IPv4 se ativado. Em algumas situações, por exemplo, quando a rede IPv4
não consegue resolver/alcançar endereços externos, isso pode causar Network
unreachable exceções e falhas de build. Nesses casos, é possível substituir o comportamento do Bazel para preferir IPv6 usando a propriedade do sistema
java.net.preferIPv6Addresses=true.
Especificamente:
Use a
--host_jvm_args=-Djava.net.preferIPv6Addresses=trueopção de inicialização, por exemplo, adicionando a seguinte linha ao arquivo.bazelrc:startup --host_jvm_args=-Djava.net.preferIPv6Addresses=trueAo executar destinos de build Java que precisam se conectar à Internet (como para testes de integração), use a
--jvmopt=-Djava.net.preferIPv6Addresses=trueflag de ferramenta. Por exemplo, inclua no seu.bazelrcarquivo:build --jvmopt=-Djava.net.preferIPv6AddressesSe você estiver usando
rules_jvm_externalpara resolução de versão de dependência, também adicione-Djava.net.preferIPv6Addresses=trueàCOURSIER_OPTSvariável de ambiente para fornecer opções de JVM para o Coursier.
As regras de repositório podem ser executadas remotamente com execução remota?
Não, ou pelo menos ainda não. Os usuários que empregam serviços de execução remota para acelerar os builds podem notar que as regras de repo ainda são executadas localmente. Por exemplo, um http_archive seria primeiro baixado para a máquina local (usando qualquer cache de download local, se aplicável), extraído e, em seguida, cada arquivo de origem seria enviado para o serviço de execução remota como um arquivo de entrada. É natural perguntar por que o serviço de execução remota não faz o download e extrai esse arquivo, economizando uma viagem de ida e volta inútil.
Parte do motivo é que as regras de repositório (e extensões de módulo) são semelhantes a "scripts" executados pelo próprio Bazel. Um executor remoto não precisa ter um Bazel instalado.
Outro motivo é que o Bazel geralmente precisa dos arquivos BUILD nos arquivos baixados e extraídos para realizar o carregamento e a análise, que são realizados localmente.
Há ideias preliminares para resolver esse problema, reimaginando as regras de repositório como regras de build, o que naturalmente permitiria que elas fossem executadas remotamente, mas, por outro lado, levantaria novas preocupações arquitetônicas (por exemplo, os comandos query poderiam precisar executar ações, complicando o design delas).
Para mais discussões anteriores sobre esse tópico, consulte Uma maneira de oferecer suporte a repositórios que precisam do Bazel para serem buscados.