Tutorial do Bazel: configurar conjuntos de ferramentas C++

Neste tutorial, usamos um cenário de exemplo para descrever como configurar os conjuntos de ferramentas C++ de um projeto. Ele é baseado em um exemplo de projeto em C++ que cria sem erros usando clang.

Conteúdo

Neste tutorial, você vai aprender a:

  • Configurar o ambiente de build
  • Configurar o conjunto de ferramentas do C++
  • Crie uma regra do Starlark que forneça configuração extra para o cc_toolchain para que o Bazel possa criar o aplicativo com clang.
  • Confirme o resultado esperado executando bazel build --config=clang_config //main:hello-world em uma máquina Linux
  • Criar o aplicativo em C++

Antes de começar

Configurar o ambiente de build

Para seguir este tutorial, é necessário que você esteja no Linux e tenha criado aplicativos C++ e instalado as ferramentas e bibliotecas apropriadas. O tutorial usa clang version 9.0.1, que você pode instalar no sistema.

Configure o ambiente de build da seguinte maneira:

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

  2. Faça o download do exemplo de projeto em C++ (link em inglês) do GitHub e coloque-o em um diretório vazio na máquina local.

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

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. Crie um arquivo .bazelrc na raiz do diretório do espaço de trabalho com o seguinte conteúdo para permitir o uso da sinalização --config:

    # Use our custom-configured c++ toolchain.
    
    build:clang_config --crosstool_top=//toolchain:clang_suite
    
    # Use --cpu as a differentiator.
    
    build:clang_config --cpu=k8
    
    # Use the default Bazel C++ toolchain to build the tools used during the
    # build.
    
    build:clang_config --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
    

Para uma entrada build:{config_name} --flag=value, a sinalização de linha de comando --config={config_name} está associada a essa sinalização específica. Consulte a documentação das sinalizações usadas: crosstool_top, cpu e host_crosstool_top.

Quando você compila o destino com bazel build --config=clang_config //main:hello-world, o Bazel usa o conjunto de ferramentas personalizado do //toolchain:clang_suite cc_toolchain_suite. Esse pacote pode listar diferentes conjuntos de ferramentas para diferentes CPUs, e é por isso que ele é diferenciado com a sinalização --cpu=k8.

Como o Bazel usa muitas ferramentas internas escritas em C++ durante a compilação, como o process-wrapper, o conjunto de ferramentas C++ padrão pré-existente é especificado para a plataforma do host. Assim, essas ferramentas são criadas usando esse conjunto de ferramentas em vez do criado neste tutorial.

Como configurar o conjunto de ferramentas do C++

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

  1. Execute o build com o seguinte comando:

    bazel build --config=clang_config //main:hello-world
    

    Como você especificou --crosstool_top=//toolchain:clang_suite no arquivo .bazelrc, o Bazel gera o seguinte erro:

    No such package `toolchain`: BUILD file not found on package path.
    

    No diretório do espaço de trabalho, crie o diretório toolchain para o pacote e um arquivo BUILD vazio dentro do diretório toolchain.

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

    No such target '//toolchain:clang_suite': target 'clang_suite' not declared
    in package 'toolchain' defined by .../toolchain/BUILD
    

    No arquivo toolchain/BUILD, defina um grupo de arquivos vazio da seguinte maneira:

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

    '//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
    

    O Bazel descobriu que a sinalização --crosstool_top aponta para uma regra que não fornece o provedor ToolchainInfo necessário. Portanto, você precisa apontar --crosstool_top para uma regra que forneça ToolchainInfo, que é a regra cc_toolchain_suite. No arquivo toolchain/BUILD, substitua o grupo de arquivos vazio pelo seguinte:

    cc_toolchain_suite(
        name = "clang_suite",
        toolchains = {
            "k8": ":k8_toolchain",
        },
    )
    

    O atributo toolchains mapeia automaticamente os valores de --cpu (e também --compiler, quando especificado) para cc_toolchain. Você ainda não definiu nenhum destino cc_toolchain, e o Bazel reclamará disso rapidamente.

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

    Rule '//toolchain:k8_toolchain' does not exist
    

    Agora você precisa definir destinos cc_toolchain para cada valor no atributo cc_toolchain_suite.toolchains. Adicione o código abaixo ao arquivo toolchain/BUILD:

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "k8_toolchain",
        toolchain_identifier = "k8-toolchain",
        toolchain_config = ":k8_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
  5. Execute o build novamente. O Bazel gera o seguinte erro:

    Rule '//toolchain:k8_toolchain_config' does not exist
    

    Em seguida, adicione um destino ":k8_toolkit_config" ao arquivo toolchain/BUILD:

    filegroup(name = "k8_toolchain_config")
    
  6. Execute o build novamente. O Bazel gera o seguinte erro:

    '//toolchain:k8_toolchain_config' does not have mandatory providers:
    'CcToolchainConfigInfo'
    

    CcToolchainConfigInfo é um provedor usado para configurar os conjuntos 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 necessário CcToolchainConfigInfo. Para usar a regra cc_toolchain_config, adicione uma instrução de carregamento a toolchains/BUILD:

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

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

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. 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 compilar 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 do 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
        )
    

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

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

     ..../BUILD:3:1: undeclared inclusion(s) in rule '//main:hello-world':
     this rule is missing dependency declarations for the following files included by 'main/hello-world.cc':
     '/usr/include/c++/9/ctime'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/c++config.h'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/os_defines.h'
     ....
    

    O Bazel precisa saber onde pesquisar os cabeçalhos incluídos. Há várias maneiras de resolver isso, como usar o atributo includes de cc_binary, mas 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. Esteja ciente de que, 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 ficar assim:

     return cc_common.create_cc_toolchain_config_info(
          ctx = ctx,
          cxx_builtin_include_directories = [ # NEW
            "/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,
     )
    
  9. Execute o comando de build novamente. Um erro como este será exibido:

    /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 C++ padrão e não consegue encontrar os símbolos. Há muitas maneiras de resolver isso, como usar o atributo linkopts de cc_binary. Aqui, ele é resolvido garantindo que qualquer destino que use o conjunto de ferramentas não precise especificar essa sinalização.

    Copie o seguinte código para 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",
          "flag_group",
          "flag_set",
          "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],
      )
    
  10. Se você executar bazel build --config=clang_config //main:hello-world, ele será criado.

Revisar seu trabalho

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

As principais conclusões são: - Você precisa especificar uma sinalização --crosstool_top na linha de comando, que precisa apontar para cc_toolchain_suite - É possível criar um atalho para uma configuração específica usando o arquivo .bazelrc. - O cc_dataset_suite pode listar cc_toolchains para diferentes CPUs e compiladores. É possível usar sinalizações de linha de comando, como --cpu, para diferenciá-las. - É necessário informar ao conjunto de ferramentas onde estão as ferramentas. Neste tutorial, há uma versão simplificada em que você acessa as ferramentas no sistema. Se você tiver interesse em uma abordagem mais completa, leia sobre os espaços de trabalho neste link. Suas ferramentas podem vir de um espaço de trabalho diferente, e você precisa disponibilizar os arquivos delas para cc_toolchain com dependências de destino em atributos, como compiler_files. O tool_paths também precisa ser mudado. - Você pode criar recursos para personalizar quais sinalizações precisam ser transmitidas para diferentes ações, seja vinculação ou qualquer outro tipo de ação.

Leia mais

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