Guia de migração do Bzlmod

Informar um problema Acessar a origem

Devido às limitações do WORKSPACE, o Bzlmod vai substituir o sistema legado WORKSPACE em versões futuras do Bazel. Este guia ajuda você a migrar seu projeto para o Bzlmod e a descartar o ESPAÇO DE TRABALHO para buscar dependências externas.

WORKSPACE x Bzlmod

O ESPAÇO DE TRABALHO e Bzlmod do Bazel oferecem recursos semelhantes com sintaxe diferente. Esta seção explica como migrar de funcionalidades específicas do WORKSPACE para Bzlmod.

Definir a raiz de um espaço de trabalho do Bazel

O arquivo ESPAÇO DE TRABALHO marca a raiz de origem de um projeto do Bazel. Essa responsabilidade é substituída por MODULE.bazel na versão 6.3 e mais recentes do Bazel. Na versão do Bazel anterior à 6.3, ainda deve haver um arquivo WORKSPACE ou WORKSPACE.bazel na raiz do espaço de trabalho, talvez com comentários como:

  • Espaço de trabalho

    # This file marks the root of the Bazel workspace.
    # See MODULE.bazel for external dependencies setup.
    

Ativar o Bzlmod no bazelrc

.bazelrc permite definir sinalizações que serão aplicadas sempre que o Bazel for executado. Para ativar o Bzlmod, use a sinalização --enable_bzlmod e aplique-a ao comando common para que ela seja aplicada a todos os comandos:

  • .bazelrc (link em inglês)

    # Enable Bzlmod for every Bazel command
    common --enable_bzlmod
    

Especifique o nome do repositório para seu espaço de trabalho

  • Espaço de trabalho

    A função workspace é usada para especificar um nome de repositório para seu espaço de trabalho. Isso permite que um //foo:bar de destino no espaço de trabalho seja referenciado como @<workspace name>//foo:bar. Se não for especificado, o nome do repositório padrão do seu espaço de trabalho será __main__.

    ## WORKSPACE
    workspace(name = "com_foo_bar")
    
  • Bzlmod (link em inglês)

    É recomendável referenciar destinos no mesmo espaço de trabalho com a sintaxe //foo:bar sem @<repo name>. No entanto, se você precisar da sintaxe antiga, poderá usar o nome do módulo especificado pela função module como o nome do repositório. Se o nome do módulo for diferente do nome do repositório necessário, use o atributo repo_name da função module para substituir o nome do repositório.

    ## MODULE.bazel
    module(
        name = "bar",
        repo_name = "com_foo_bar",
    )
    

Buscar dependências externas como módulos do Bazel

Se a dependência for um projeto do Bazel, você poderá depender dele como um módulo do Bazel quando ele também adotar o Bzlmod.

  • Espaço de trabalho

    Com o ESPAÇO DE TRABALHO, é comum usar as regras de repositório http_archive ou git_repository para fazer o download das origens do projeto do Bazel.

    ## WORKSPACE
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    http_archive(
        name = "bazel_skylib",
        urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz"],
        sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa",
    )
    load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
    bazel_skylib_workspace()
    
    http_archive(
        name = "rules_java",
        urls = ["https://github.com/bazelbuild/rules_java/releases/download/6.1.1/rules_java-6.1.1.tar.gz"],
        sha256 = "76402a50ae6859d50bd7aed8c1b8ef09dae5c1035bb3ca7d276f7f3ce659818a",
    )
    load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
    rules_java_dependencies()
    rules_java_toolchains()
    

    Como você pode notar, é um padrão comum que os usuários precisam carregar dependências transitivas de uma macro da dependência. Suponha que bazel_skylib e rules_java dependam de platform. A versão exata da dependência platform é determinada pela ordem das macros.

  • Bzlmod (link em inglês)

    Com o Bzlmod, desde que a dependência esteja disponível no Bazel Central Registry ou no seu registro do Bazel personalizado, você pode simplesmente depender dele com uma diretiva bazel_dep.

    ## MODULE.bazel
    bazel_dep(name = "bazel_skylib", version = "1.4.2")
    bazel_dep(name = "rules_java", version = "6.1.1")
    

    O Bzlmod resolve as dependências de módulo do Bazel de forma transitiva usando o algoritmo MVS. Portanto, a versão máxima necessária de platform é selecionada automaticamente.

Substituir uma dependência como um módulo do Bazel

Como módulo raiz, é possível modificar as dependências do módulo do Bazel de maneiras diferentes.

Leia a seção sobre overrides para mais informações.

Confira alguns exemplos de uso no repositório de exemplos.

Buscar dependências externas com extensões de módulo

Se a dependência não for um projeto do Bazel ou ainda não estiver disponível em nenhum registro do Bazel, use extensões de módulo para apresentá-la.

  • Espaço de trabalho

    Faça o download de um arquivo usando a regra de repositório http_file.

    ## WORKSPACE
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
    
    http_file(
        name = "data_file",
        url = "http://example.com/file",
        sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    )
    
  • Bzlmod (link em inglês)

    Com o Bzlmod, você precisa mover a definição para um arquivo .bzl, que também permite compartilhar a definição entre o WORKSPACE e Bzlmod durante o período de migração.

    ## repositories.bzl
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
    def my_data_dependency():
        http_file(
            name = "data_file",
            url = "http://example.com/file",
            sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        )
    

    Implemente uma extensão de módulo para carregar a macro de dependências. Você pode defini-la no mesmo arquivo .bzl da macro, mas, para manter a compatibilidade com versões mais antigas do Bazel, é melhor defini-la em um arquivo .bzl separado.

    ## extensions.bzl
    load("//:repositories.bzl", "my_data_dependency")
    def _non_module_dependencies_impl(_ctx):
        my_data_dependency()
    
    non_module_dependencies = module_extension(
        implementation = _non_module_dependencies_impl,
    )
    

    Para tornar o repositório visível para o projeto raiz, declare os usos da extensão do módulo e do repositório no arquivo MODULE.bazel.

    ## MODULE.bazel
    non_module_dependencies = use_extension("//:extensions.bzl", "non_module_dependencies")
    use_repo(non_module_dependencies, "data_file")
    

Resolver conflitos de dependências externas com extensão do módulo

Um projeto pode fornecer uma macro que introduz repositórios externos com base nas entradas dos autores da chamada. Mas e se houver vários autores de chamada no gráfico de dependência e eles causarem um conflito?

Suponha que o projeto foo forneça a seguinte macro, que usa version como um argumento.

## repositories.bzl in foo {:#repositories.bzl-foo}
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
def data_deps(version = "1.0"):
    http_file(
        name = "data_file",
        url = "http://example.com/file-%s" % version,
        # Omitting the "sha256" attribute for simplicity
    )
  • Espaço de trabalho

    Com o ESPAÇO DE TRABALHO, você pode carregar a macro de @foo e especificar a versão da dependência de dados necessária. Suponha que você tenha outra dependência @bar, que também depende de @foo, mas exige uma versão diferente da dependência de dados.

    ## WORKSPACE
    
    # Introduce @foo and @bar.
    ...
    
    load("@foo//:repositories.bzl", "data_deps")
    data_deps(version = "2.0")
    
    load("@bar//:repositories.bzl", "bar_deps")
    bar_deps() # -> which calls data_deps(version = "3.0")
    

    Nesse caso, o usuário final precisa ajustar cuidadosamente a ordem das macros no Espaço de trabalho para chegar à versão necessária. Esse é um dos maiores pontos problemáticos do WORKSPACE, já que ele não oferece uma maneira sensata de resolver dependências.

  • Bzlmod (link em inglês)

    Com o Bzlmod, o autor do projeto foo pode usar a extensão do módulo para resolver conflitos. Por exemplo, vamos supor que faça sentido sempre selecionar a versão máxima necessária da dependência de dados entre todos os módulos do Bazel.

    ## extensions.bzl in foo
    load("//:repositories.bzl", "data_deps")
    
    data = tag_class(attrs={"version": attr.string()})
    
    def _data_deps_extension_impl(module_ctx):
        # Select the maximal required version in the dependency graph.
        version = "1.0"
        for mod in module_ctx.modules:
            for data in mod.tags.data:
                version = max(version, data.version)
        data_deps(version)
    
    data_deps_extension = module_extension(
        implementation = _data_deps_extension_impl,
        tag_classes = {"data": data},
    )
    
    ## MODULE.bazel in bar
    bazel_dep(name = "foo", version = "1.0")
    
    foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension")
    foo_data_deps.data(version = "3.0")
    use_repo(foo_data_deps, "data_file")
    
    ## MODULE.bazel in root module
    bazel_dep(name = "foo", version = "1.0")
    bazel_dep(name = "bar", version = "1.0")
    
    foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension")
    foo_data_deps.data(version = "2.0")
    use_repo(foo_data_deps, "data_file")
    

    Nesse caso, o módulo raiz exige a versão de dados 2.0, enquanto a dependência bar exige 3.0. A extensão do módulo em foo pode resolver corretamente esse conflito e selecionar automaticamente a versão 3.0 para a dependência de dados.

Integrar um gerenciador de pacotes de terceiros

Seguindo a última seção, como a extensão do módulo fornece uma maneira de coletar informações do gráfico de dependência, executar uma lógica personalizada para resolver dependências e chamar regras de repositório para introduzir repositórios externos, oferece uma ótima maneira para os autores de regras melhorarem os conjuntos de regras que integram gerenciadores de pacotes para linguagens específicas.

Leia a página Extensões de módulo para saber mais sobre como usar essas extensões.

Veja uma lista dos conjuntos de regras que já adotaram o Bzlmod para buscar dependências de diferentes gerenciadores de pacotes:

Um exemplo mínimo que integra um pseudogerenciador de pacotes está disponível no repositório de exemplos.

Detectar conjuntos de ferramentas na máquina host

Quando as regras de compilação do Bazel precisam detectar quais conjuntos de ferramentas estão disponíveis na máquina host, elas usam regras de repositório para inspecionar a máquina host e gerar informações do conjunto de ferramentas como repositórios externos.

  • Espaço de trabalho

    Considere a regra de repositório a seguir para detectar um conjunto de ferramentas de shell.

    ## local_config_sh.bzl
    def _sh_config_rule_impl(repository_ctx):
        sh_path = get_sh_path_from_env("SH_BIN_PATH")
    
        if not sh_path:
            sh_path = detect_sh_from_path()
    
        if not sh_path:
            sh_path = "/shell/binary/not/found"
    
        repository_ctx.file("BUILD", """
    load("@bazel_tools//tools/sh:sh_toolchain.bzl", "sh_toolchain")
    sh_toolchain(
        name = "local_sh",
        path = "{sh_path}",
        visibility = ["//visibility:public"],
    )
    toolchain(
        name = "local_sh_toolchain",
        toolchain = ":local_sh",
        toolchain_type = "@bazel_tools//tools/sh:toolchain_type",
    )
    """.format(sh_path = sh_path))
    
    sh_config_rule = repository_rule(
        environ = ["SH_BIN_PATH"],
        local = True,
        implementation = _sh_config_rule_impl,
    )
    

    É possível carregar a regra de repositório no Espaço de Trabalho.

    ## WORKSPACE
    load("//:local_config_sh.bzl", "sh_config_rule")
    sh_config_rule(name = "local_config_sh")
    
  • Bzlmod (link em inglês)

    Com o Bzlmod, você pode introduzir o mesmo repositório usando uma extensão de módulo, que é semelhante à introdução do repositório @data_file na última seção.

    ## local_config_sh_extension.bzl
    load("//:local_config_sh.bzl", "sh_config_rule")
    
    sh_config_extension = module_extension(
        implementation = lambda ctx: sh_config_rule(name = "local_config_sh"),
    )
    

    Em seguida, use a extensão no arquivo MODULE.bazel.

    ## MODULE.bazel
    sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension")
    use_repo(sh_config_ext, "local_config_sh")
    

Registrar conjuntos de ferramentas e plataformas de execução

Após a introdução de um repositório que hospeda informações do conjunto de ferramentas (por exemplo, local_config_sh), você provavelmente vai querer registrar o conjunto de ferramentas.

  • Espaço de trabalho

    Com o WORKSPACE, é possível registrar o conjunto de ferramentas das seguintes maneiras.

    1. Você pode registrar o conjunto de ferramentas do arquivo .bzl e carregar a macro no arquivo do ESPAÇO DE TRABALHO.

      ## local_config_sh.bzl
      def sh_configure():
          sh_config_rule(name = "local_config_sh")
          native.register_toolchains("@local_config_sh//:local_sh_toolchain")
      
      ## WORKSPACE
      load("//:local_config_sh.bzl", "sh_configure")
      sh_configure()
      
    2. Ou registre o conjunto de ferramentas diretamente no arquivo WORKSPACE.

      ## WORKSPACE
      load("//:local_config_sh.bzl", "sh_config_rule")
      sh_config_rule(name = "local_config_sh")
      register_toolchains("@local_config_sh//:local_sh_toolchain")
      
  • Bzlmod (link em inglês)

    Com o Bzlmod, as APIs register_toolchains e register_execution_platforms estão disponíveis apenas no arquivo MODULE.bazel. Não é possível chamar native.register_toolchains em uma extensão de módulo.

    ## MODULE.bazel
    sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension")
    use_repo(sh_config_ext, "local_config_sh")
    register_toolchains("@local_config_sh//:local_sh_toolchain")
    

Os conjuntos de ferramentas e as plataformas de execução registrados em WORKSPACE, WORKSPACE.bzlmod e o arquivo MODULE.bazel de cada módulo do Bazel seguem esta ordem de precedência durante a seleção do conjunto de ferramentas (do maior para o menor):

  1. de conjuntos de ferramentas e plataformas de execução registradas no arquivo MODULE.bazel do módulo raiz.
  2. de conjuntos de ferramentas e plataformas de execução registradas no arquivo WORKSPACE ou WORKSPACE.bzlmod.
  3. conjuntos de ferramentas e plataformas de execução registrados por módulos que são dependências (transitivas) do módulo raiz.
  4. Quando não estiver usando WORKSPACE.bzlmod: conjuntos de ferramentas registrados no sufixo WORKSPACE.

Introdução aos repositórios locais

Pode ser necessário introduzir uma dependência como repositório local quando precisar de uma versão local da dependência para depuração ou quiser incorporar um diretório em seu espaço de trabalho como repositório externo.

  • Espaço de trabalho

    Com o ESPAÇO DE TRABALHO, isso é possível com duas regras de repositório nativas, local_repository e new_local_repository.

    ## WORKSPACE
    local_repository(
        name = "rules_java",
        path = "/Users/bazel_user/workspace/rules_java",
    )
    
  • Bzlmod (link em inglês)

    Com o Bzlmod, você pode usar local_path_override para substituir um módulo por um caminho local.

    ## MODULE.bazel
    bazel_dep(name = "rules_java")
    local_path_override(
        module_name = "rules_java",
        path = "/Users/bazel_user/workspace/rules_java",
    )
    

    Também é possível introduzir um repositório local com a extensão do módulo. No entanto, não é possível chamar native.local_repository na extensão do módulo. Há um esforço contínuo para colocar em estrela todas as regras de repositório nativas (consulte #18285 para ver o progresso). Em seguida, você pode chamar o local_repository do Starlark correspondente em uma extensão de módulo. Também é simples implementar uma versão personalizada da regra de repositório local_repository se esse for um problema de bloqueio para você.

Vincular destinos

A regra bind no ESPAÇO DE TRABALHO foi descontinuada e não tem suporte em Bzlmod. Ele foi introduzido para fornecer um alias a um destino no pacote //external especial. Todos os usuários que dependem disso precisam migrar.

Por exemplo, se você tiver

## WORKSPACE
bind(
    name = "openssl",
    actual = "@my-ssl//src:openssl-lib",
)

Isso permite que outros destinos dependam de //external:openssl. É possível migrar dessa forma:

  • Substitua todos os usos de //external:openssl por @my-ssl//src:openssl-lib.

  • Ou use a regra de build alias

    • Defina o seguinte destino em um pacote (por exemplo, //third_party)

      ## third_party/BUILD
      alias(
          name = "openssl,
          actual = "@my-ssl//src:openssl-lib",
      )
      
    • Substitua todos os usos de //external:openssl por //third_party:openssl-lib.

Migração

Nesta seção, você encontra informações e orientações úteis para o processo de migração do Bzlmod.

Conhecer suas dependências no WORKSPACE

A primeira etapa da migração é entender quais dependências você tem. Pode ser difícil descobrir quais dependências exatas são introduzidas no arquivo do ESPAÇO DE TRABALHO porque as dependências transitivas geralmente são carregadas com macros *_deps.

Inspecionar a dependência externa com o arquivo resolvido do espaço de trabalho

Felizmente, a flag --experimental_repository_resolved_file pode ajudar. Essa sinalização basicamente gera um "arquivo de bloqueio" de todas as dependências externas buscadas no último comando do Bazel. Veja mais detalhes nesta postagem do blog (link em inglês).

Ele pode ser usado de duas maneiras:

  1. Buscar informações de dependências externas necessárias para criar determinados destinos.

    bazel clean --expunge
    bazel build --nobuild --experimental_repository_resolved_file=resolved.bzl //foo:bar
    
  2. Buscar informações de todas as dependências externas definidas no arquivo ESPAÇO DE TRABALHO.

    bazel clean --expunge
    bazel sync --experimental_repository_resolved_file=resolved.bzl
    

    Com o comando bazel sync, é possível buscar todas as dependências definidas no arquivo do ESPAÇO DE TRABALHO, que incluem:

    • Usos do bind
    • Usos de register_toolchains e register_execution_platforms

    No entanto, se o projeto for entre plataformas, a sincronização do Bazel poderá ser interrompida em determinadas plataformas, porque algumas regras de repositório só poderão ser executadas corretamente em plataformas compatíveis.

Depois de executar o comando, você terá informações das dependências externas no arquivo resolved.bzl.

Inspecionar a dependência externa com bazel query

Talvez você também saiba que bazel query pode ser usado para inspecionar regras de repositório com

bazel query --output=build //external:<repo name>

Embora seja mais conveniente e muito mais rápida, a consulta do Bazel pode estar relacionada à versão de dependência externa, portanto, tenha cuidado ao usá-la. A consulta e a inspeção de dependências externas com o Bzlmod serão realizadas por um novo subcomando.

Dependências padrão integradas

Se você verificar o arquivo gerado por --experimental_repository_resolved_file, encontrará muitas dependências que não estão definidas no seu ESPAÇO DE TRABALHO. Isso ocorre porque o Bazel adiciona prefixos e sufixos ao conteúdo do arquivo WORKSPACE do usuário para injetar algumas dependências padrão, que geralmente são exigidas pelas regras nativas (por exemplo, @bazel_tools, @platforms e @remote_java_tools). Com o Bzlmod, essas dependências são introduzidas com um módulo integrado bazel_tools, que é uma dependência padrão para todos os outros módulos do Bazel.

Modo híbrido para migração gradual

Bzlmod e WORKSPACE podem trabalhar lado a lado, o que permite que a migração de dependências do arquivo WORKSPACE para Bzlmod seja um processo gradual.

WORKSPACE.bzlmod

Durante a migração, os usuários do Bazel podem precisar alternar entre builds com e sem o Bzlmod ativado. O suporte a WORKSPACE.bzlmod foi implementado para facilitar o processo.

WORKSPACE.bzlmod tem exatamente a mesma sintaxe que WORKSPACE. Quando a Bzlmod estiver ativada, se um arquivo WORKSPACE.bzlmod também existir na raiz do espaço de trabalho:

  • WORKSPACE.bzlmod entra em vigor, e o conteúdo de WORKSPACE é ignorado.
  • Nenhum prefixo ou sufixo é adicionado ao arquivo WORKSPACE.bzlmod.

O uso do arquivo WORKSPACE.bzlmod pode facilitar a migração porque:

  • Quando o Bzlmod está desativado, você volta para buscar dependências do arquivo WORKSPACE original.
  • Quando a biblioteca Bzlmod está ativada, é possível monitorar melhor quais dependências faltam para migração com WORKSPACE.bzlmod.

Visibilidade do repositório

O Bzlmod pode controlar quais outros repositórios são visíveis em um determinado repositório. Verifique os nomes dos repositórios e dependências estritas para mais detalhes.

Veja um resumo das visibilidades do repositório de diferentes tipos de repositórios ao considerar também o WORKSPACE.

Do repositório principal De repositórios de módulos do Bazel De repositórios de extensão do módulo Dos repositórios do WORKSPACE
O repositório principal Visible Se o módulo raiz for uma dependência direta Se o módulo raiz for uma dependência direta do módulo que hospeda a extensão do módulo Visible
Repositórios de módulos do Bazel Dependências diretas Dependências diretas Dependências diretas do módulo que hospeda a extensão do módulo Dependências diretas do módulo raiz
Repositórios de extensões do módulo Dependências diretas Dependências diretas Dependências diretas do módulo que hospeda a extensão do módulo + todos os repositórios gerados pela mesma extensão do módulo Dependências diretas do módulo raiz
Repositórios do WORKSPACE Todos visíveis Não visível Não visível Todos visíveis