Tutorial do Bazel: configurar conjuntos de ferramentas C++

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Este tutorial usa um cenário de exemplo para descrever como configurar as cadeias de ferramentas C++ para um projeto.

O que você vai aprender

Neste tutorial, você vai aprender a:

  • Configurar o ambiente de build
  • Usar --toolchain_resolution_debug para depurar a resolução do conjunto de ferramentas
  • Configurar o conjunto de ferramentas do C++
  • Crie uma regra do Starlark que ofereça uma configuração adicional para o cc_toolchain para que o Bazel possa criar o aplicativo com clang.
  • Crie o binário C++ executando bazel build //main:hello-world em uma máquina Linux.
  • Faça a compilação cruzada do binário para Android executando bazel build //main:hello-world --platforms=//:android_x86_64.

Antes de começar

Neste tutorial, presumimos que você esteja no Linux e tenha criado aplicativos C++ e instalado as ferramentas e bibliotecas adequadas. No tutorial, usamos clang version 16, que você pode instalar no sistema.

Configurar o ambiente de build

Configure o ambiente de build da seguinte forma:

  1. Faça o download e instale o Bazel 7.0.2 ou uma versão mais recente, caso ainda não tenha feito isso.

  2. Adicione um arquivo MODULE.bazel vazio na pasta raiz.

  3. Adicione o seguinte destino cc_binary ao arquivo main/BUILD:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    

    Como o Bazel usa muitas ferramentas internas escritas em C++ durante o build, como process-wrapper, o conjunto de ferramentas C++ padrão preexistente é especificado para a plataforma host. Isso permite que essas ferramentas internas sejam criadas usando a toolchain criada neste tutorial. Portanto, o destino cc_binary também é criado com o conjunto de ferramentas padrão.

  4. Execute o build com o seguinte comando:

    bazel build //main:hello-world
    

    O build é bem-sucedido sem nenhuma cadeia de ferramentas registrada em MODULE.bazel.

    Para conferir mais detalhes, execute:

    bazel build //main:hello-world --toolchain_resolution_debug='@bazel_tools//tools/cpp:toolchain_type'
    
    INFO: ToolchainResolution: Target platform @@platforms//host:host: Selected execution platform @@platforms//host:host, type @@bazel_tools//tools/cpp:toolchain_type -> toolchain @@bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8
    

    Sem especificar --platforms, o Bazel cria o destino para @platforms//host usando @bazel_tools+cc_configure_extension+local_config_cc//:cc-compiler-k8.

Configurar o conjunto de ferramentas do C++

Para configurar o conjunto de ferramentas C++, crie o aplicativo várias vezes e elimine cada erro um por um, conforme descrito abaixo.

Ele também assume clang version 9.0.1, embora os detalhes mudem apenas um pouco entre as diferentes versões do clang.

  1. Adicionar toolchain/BUILD com

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "linux_x86_64_toolchain",
        toolchain_identifier = "linux_x86_64-toolchain",
        toolchain_config = ":linux_x86_64_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
    toolchain(
        name = "cc_toolchain_for_linux_x86_64",
        toolchain = ":linux_x86_64_toolchain",
        toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
        exec_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
        target_compatible_with = [
            "@platforms//cpu:x86_64",
            "@platforms//os:linux",
        ],
    )
    

    Em seguida, adicione as dependências apropriadas e registre o conjunto de ferramentas com MODULE.bazel com

    bazel_dep(name = "platforms", version = "0.0.10")
    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64"
    )
    

    Esta etapa define um cc_toolchain e o vincula a um destino toolchain para a configuração do host.

  2. Execute o build novamente. Como o pacote toolchain ainda não define o destino linux_x86_64_toolchain_config, o Bazel gera o seguinte erro:

    ERROR: toolchain/BUILD:4:13: in toolchain_config attribute of cc_toolchain rule //toolchain:linux_x86_64_toolchain: rule '//toolchain:linux_x86_64_toolchain_config' does not exist.
    
  3. No arquivo toolchain/BUILD, defina um grupo de arquivos vazio da seguinte maneira:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "linux_x86_64_toolchain_config")
    
  4. Execute o build novamente. O Bazel gera o seguinte erro:

    '//toolchain:linux_x86_64_toolchain_config' does not have mandatory providers: 'CcToolchainConfigInfo'.
    

    CcToolchainConfigInfo é um provedor usado para configurar as cadeias de ferramentas C++. Para corrigir esse erro, crie uma regra do Starlark que forneça CcToolchainConfigInfo ao Bazel criando um arquivo toolchain/cc_toolchain_config.bzl com o seguinte conteúdo:

    def _impl(ctx):
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "k8-toolchain",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
        )
    
    cc_toolchain_config = rule(
        implementation = _impl,
        attrs = {},
        provides = [CcToolchainConfigInfo],
    )
    

    cc_common.create_cc_toolchain_config_info() cria o provedor CcToolchainConfigInfo necessário. Para usar a regra cc_toolchain_config, adicione uma instrução de carga a toolchain/BUILD logo abaixo da instrução de pacote:

    load(":cc_toolchain_config.bzl", "cc_toolchain_config")
    

    E substitua o grupo de arquivos "linux_x86_64_Dataset_config" por uma declaração de uma regra cc_toolchain_config:

    cc_toolchain_config(name = "linux_x86_64_toolchain_config")
    
  5. Execute o build novamente. O Bazel gera o seguinte erro:

    .../BUILD:1:1: C++ compilation of rule '//:hello-world' failed (Exit 1)
    src/main/tools/linux-sandbox-pid1.cc:421:
    "execvp(toolchain/DUMMY_GCC_TOOL, 0x11f20e0)": No such file or directory
    Target //:hello-world failed to build`
    

    Nesse ponto, o Bazel tem informações suficientes para tentar criar o código, mas ainda não sabe quais ferramentas usar para concluir as ações de build necessárias. Você modificará a implementação da regra Starlark para informar ao Bazel quais ferramentas usar. Para isso, você precisa do construtor tool_path() de @bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl:

    # toolchain/cc_toolchain_config.bzl:
    # NEW
    load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "tool_path")
    
    def _impl(ctx):
        tool_paths = [ # NEW
            tool_path(
                name = "gcc",
                path = "/usr/bin/clang",
            ),
            tool_path(
                name = "ld",
                path = "/usr/bin/ld",
            ),
            tool_path(
                name = "ar",
                path = "/usr/bin/ar",
            ),
            tool_path(
                name = "cpp",
                path = "/bin/false",
            ),
            tool_path(
                name = "gcov",
                path = "/bin/false",
            ),
            tool_path(
                name = "nm",
                path = "/bin/false",
            ),
            tool_path(
                name = "objdump",
                path = "/bin/false",
            ),
            tool_path(
                name = "strip",
                path = "/bin/false",
            ),
        ]
    
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "local",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            tool_paths = tool_paths, # NEW
        )
    

    Verifique se /usr/bin/clang e /usr/bin/ld são os caminhos corretos para seu sistema.

  6. Execute o build novamente. O Bazel gera o seguinte erro:

    ERROR: main/BUILD:3:10: Compiling main/hello-world.cc failed: absolute path inclusion(s) found in rule '//main:hello-world':
    the source file 'main/hello-world.cc' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain):
      '/usr/include/c++/13/ctime'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/c++config.h'
      '/usr/include/x86_64-linux-gnu/c++/13/bits/os_defines.h'
      ...
    

    O Bazel precisa saber onde procurar os cabeçalhos incluídos. Há várias maneiras de resolver isso, como usar o atributo includes de cc_binary, mas aqui isso é resolvido no nível do conjunto de ferramentas com o parâmetro cxx_builtin_include_directories de cc_common.create_cc_toolchain_config_info. Se você estiver usando uma versão diferente de clang, o caminho de inclusão será diferente. Esses caminhos também podem ser diferentes dependendo da distribuição.

    Modifique o valor de retorno em toolchain/cc_toolchain_config.bzl para este formato:

    return cc_common.create_cc_toolchain_config_info(
        ctx = ctx,
        cxx_builtin_include_directories = [ # NEW
            "/usr/lib/llvm-16/lib/clang/16/include",
            "/usr/include",
        ],
        toolchain_identifier = "local",
        host_system_name = "local",
        target_system_name = "local",
        target_cpu = "k8",
        target_libc = "unknown",
        compiler = "clang",
        abi_version = "unknown",
        abi_libc_version = "unknown",
        tool_paths = tool_paths,
    )
    
  7. Execute o comando de build novamente. Um erro como este vai aparecer:

    /usr/bin/ld: bazel-out/k8-fastbuild/bin/main/_objs/hello-world/hello-world.o: in function `print_localtime()':
    hello-world.cc:(.text+0x68): undefined reference to `std::cout'
    

    Isso ocorre porque o vinculador não tem a biblioteca padrão C++ e não consegue encontrar os símbolos. Há muitas maneiras de resolver esse problema, como usar o atributo linkopts de cc_binary. Aqui, o problema é resolvido garantindo que qualquer destino que use o conjunto de ferramentas não precise especificar essa flag.

    Copie o seguinte código para toolchain/cc_toolchain_config.bzl:

    # NEW
    load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
    # NEW
    load(
        "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
        "feature",    # NEW
        "flag_group", # NEW
        "flag_set",   # NEW
        "tool_path",
    )
    
    all_link_actions = [ # NEW
        ACTION_NAMES.cpp_link_executable,
        ACTION_NAMES.cpp_link_dynamic_library,
        ACTION_NAMES.cpp_link_nodeps_dynamic_library,
    ]
    
    def _impl(ctx):
        tool_paths = [
            tool_path(
                name = "gcc",
                path = "/usr/bin/clang",
            ),
            tool_path(
                name = "ld",
                path = "/usr/bin/ld",
            ),
            tool_path(
                name = "ar",
                path = "/bin/false",
            ),
            tool_path(
                name = "cpp",
                path = "/bin/false",
            ),
            tool_path(
                name = "gcov",
                path = "/bin/false",
            ),
            tool_path(
                name = "nm",
                path = "/bin/false",
            ),
            tool_path(
                name = "objdump",
                path = "/bin/false",
            ),
            tool_path(
                name = "strip",
                path = "/bin/false",
            ),
        ]
    
        features = [ # NEW
            feature(
                name = "default_linker_flags",
                enabled = True,
                flag_sets = [
                    flag_set(
                        actions = all_link_actions,
                        flag_groups = ([
                            flag_group(
                                flags = [
                                    "-lstdc++",
                                ],
                            ),
                        ]),
                    ),
                ],
            ),
        ]
    
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            features = features, # NEW
            cxx_builtin_include_directories = [
                "/usr/lib/llvm-9/lib/clang/9.0.1/include",
                "/usr/include",
            ],
            toolchain_identifier = "local",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            tool_paths = tool_paths,
        )
    
    cc_toolchain_config = rule(
        implementation = _impl,
        attrs = {},
        provides = [CcToolchainConfigInfo],
    )
    
  8. Ao executar bazel build //main:hello-world, ele finalmente criará o binário para o host.

  9. Em toolchain/BUILD, copie os destinos cc_toolchain_config, cc_toolchain e toolchain e substitua linux_x86_64 por android_x86_64nos nomes de destino.

    Em MODULE.bazel, registre a cadeia de ferramentas para Android

    register_toolchains(
        "//toolchain:cc_toolchain_for_linux_x86_64",
        "//toolchain:cc_toolchain_for_android_x86_64"
    )
    
  10. Execute bazel build //main:hello-world --android_platforms=//toolchain:android_x86_64 para criar o binário para Android.

Na prática, o Linux e o Android precisam ter configurações diferentes da cadeia de ferramentas C++. É possível modificar o cc_toolchain_config atual para as diferenças ou criar regras separadas (ou seja, provedor CcToolchainConfigInfo) para plataformas separadas.

Revisar seu trabalho

Neste tutorial, você aprendeu a configurar uma cadeia de ferramentas C++ básica, mas as cadeias de ferramentas são mais poderosas do que este exemplo.

Os principais tópicos são:

  • É necessário especificar uma sinalização platforms correspondente na linha de comando para que o Bazel resolva o conjunto de ferramentas para os mesmos valores de restrição na plataforma. A documentação contém mais informações sobre flags de configuração de linguagens específicas.
  • Você precisa informar à cadeia de ferramentas onde as ferramentas estão. Neste tutorial, há uma versão simplificada em que você acessa as ferramentas do sistema. Se você tiver interesse em uma abordagem mais independente, leia sobre dependências externas. As ferramentas podem vir de um módulo diferente, e você precisa disponibilizar os arquivos para o cc_toolchain com dependências de destino em atributos, como compiler_files. O tool_paths também precisa ser alterado.
  • É possível criar recursos para personalizar quais flags precisam ser transmitidas para ações diferentes, seja vinculação ou qualquer outro tipo de ação.

Leitura adicional

Para saber mais, consulte Configuração do conjunto de ferramentas C++.