Perguntas frequentes

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.bazel e 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.bazel no arquivo de origem definir version = "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.0 nã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_version em 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 include diretiva para dividir o arquivo MODULE.bazel em vários segmentos. Pelo mesmo motivo que não permitimos loads em arquivos MODULE.bazel, include nã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 (@@):
    1. Se bar for um nome de repositório aparente introduzido pelo arquivo MODULE.bazel do módulo raiz (por qualquer um de bazel_dep, use_repo, module, use_repo_rule), então @bar será resolvido para o que esse arquivo MODULE.bazel afirma.
    2. Caso contrário, se bar for um repositório definido no WORKSPACE (o que significa que o nome canônico é @@bar), então @bar será resolvido para @@bar.
    3. Caso contrário, @bar será resolvido para algo como @@[unknown repo 'bar' requested from @@], e isso acabará resultando em um erro.
  • 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_deps no módulo raiz ou instâncias use_repo_rule no módulo raiz.
  • Se o repositório de contexto for definido no WORKSPACE:
    1. 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 com repo_mapping = {"@bar": "@baz"}, vamos analisar at @baz abaixo).
    2. Se bar for um nome de repositório aparente introduzido pelo arquivo MODULE.bazel do módulo raiz, então @bar será resolvido para o que esse arquivo MODULE.bazel afirma. Isso é o mesmo que o item 1 no caso do repositório principal.
    3. Caso contrário, @bar será resolvido para @@bar. Isso provavelmente vai apontar para um repositório bar definido no WORKSPACE. Se esse repositório não estiver definido, o Bazel vai gerar um erro.

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=true opção de inicialização, por exemplo, adicionando a seguinte linha ao arquivo .bazelrc:

    startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true

  • Ao executar destinos de build Java que precisam se conectar à Internet (como para testes de integração), use a --jvmopt=-Djava.net.preferIPv6Addresses=true flag de ferramenta. Por exemplo, inclua no seu .bazelrc arquivo:

    build --jvmopt=-Djava.net.preferIPv6Addresses

  • Se você estiver usando rules_jvm_external para resolução de versão de dependência, também adicione -Djava.net.preferIPv6Addresses=true à COURSIER_OPTS variá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.