Tutorial do Bazel: configurar conjuntos de ferramentas C++

Relatar um problema Conferir código-fonte Por noite · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Este tutorial usa um cenário de exemplo para descrever como configurar C++ de última geração 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 Starlark que forneça mais configuração para o cc_toolchain para que o Bazel possa criar o aplicativo com clang
  • Para criar o binário C++, execute 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 usando o Linux e tenha criado aplicativos em C++ e instalou as ferramentas e bibliotecas adequadas. O tutorial usa clang version 16, que pode ser instalado no sistema.

Configurar o ambiente de build

Configure o ambiente de build da seguinte forma:

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

  2. Adicione um arquivo WORKSPACE vazio na pasta raiz.

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

    load("@rules_cc//cc:defs.bzl", "cc_binary")
    
    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    

    Como o Bazel usa muitas ferramentas internas escritas em C++ durante a compilação, como como process-wrapper, o conjunto de ferramentas C++ padrão preexistente será especificado para na plataforma de hospedagem. Assim, essas ferramentas internas podem ser criadas usando conjunto de ferramentas do criado 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
    

    A compilação é bem-sucedida sem nenhum conjunto de ferramentas registrado no WORKSPACE.

    Para conferir mais detalhes, execute:

    bazel build //main:hello-world --toolchain_resolution_debug='@bazel_tools//tools/cpp:toolchain_type'
    
    INFO: ToolchainResolution: Target platform @@local_config_platform//:host: Selected execution platform @@local_config_platform//: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 @local_config_platform//:host em @bazel_tools//cc_configure_extension/local_config_cc//:cc-compiler-k8

Configurar o conjunto de ferramentas do C++

Para configurar o conjunto de ferramentas para C++, crie o aplicativo repetidamente e elimine cada erro, um por um, conforme descrito a seguir.

Ele também pressupõe clang version 9.0.1, embora os detalhes devam mudar apenas 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, registre o conjunto de ferramentas com o WORKSPACE com

    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 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 que você usa para configurar seus conjuntos de ferramentas C++. Para corrigir esse erro, crie uma regra do Starlark que fornece CcToolchainConfigInfo ao Bazel fazendo uma 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 necessário CcToolchainConfigInfo. Para usar a regra cc_toolchain_config, adicione um carregamento para toolchain/BUILD logo abaixo da instrução do pacote:

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

    E substitua "linux_x86_64_Dataset_config" grupo de arquivos com 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`
    

    Neste ponto, o Bazel tem informações suficientes para tentar criar o código, mas ele ainda não sabe quais ferramentas usar para concluir a compilação necessária ações. Você vai modificar a implementação da regra Starlark para informar ao Bazel o que usar. Para isso, você precisa do construtor tool_path() da @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 pesquisar os cabeçalhos incluídos. Existem várias maneiras de resolver isso, como usar o atributo includes do cc_binary, mas aqui isso é resolvido no nível do conjunto de ferramentas com o cxx_builtin_include_directories de cc_common.create_cc_toolchain_config_info. Esteja ciente de que se 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 procurar assim:

    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. Você verá um erro como este:

    /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'
    

    O motivo disso é que o vinculador não tem o padrão C++. e não consegue encontrar os símbolos. Há muitas maneiras de resolver isso, como o uso do atributo linkopts de cc_binary. Aqui é resolvido pela garantindo que qualquer destino que use o conjunto de ferramentas não precise especificar essa sinalização.

    Copie o código abaixo 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 cc_toolchain_config, cc_toolchain e toolchain destinos e substituir linux_x86_64 por android_x86_64na meta nomes de domínio.

    Em WORKSPACE, registre o conjunto 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 de conjunto de ferramentas C++ diferentes. Você pode modificar o cc_toolchain_config existente conforme as diferenças ou criar uma regra separada (por exemplo, CcToolchainConfigInfo provedor) para plataformas.

Revise seu trabalho

Neste tutorial, você aprendeu como configurar um conjunto de ferramentas C++ básico, mas os conjuntos de ferramentas são mais poderosos do que este exemplo simples.

As principais conclusões são:

  • Você precisa especificar uma flag platforms correspondente na linha de comando para o Bazel resolver para 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 específicas de linguagem.
  • Você precisa permitir que o conjunto de ferramentas saiba onde as ferramentas estão. Neste tutorial há uma versão simplificada em que você acessa as ferramentas do sistema. Se estiver interessado em uma abordagem mais independente, leia sobre espaços de trabalho. Suas ferramentas podem vir de um espaço de trabalho diferente e você teria que disponibilizar os arquivos deles ao cc_toolchain com dependências de destino em atributos, como compiler_files. O tool_paths também precisa ser modificado.
  • É possível criar recursos para personalizar quais flags serão passadas para ações diferentes, seja vinculação ou qualquer outro tipo de ação.

Leitura adicional

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