Este tutorial usa um cenário de exemplo para descrever como configurar as cadeias de ferramentas
C++ para um projeto. Ele é baseado em um
exemplo de projeto em C++
que cria sem erros usando clang
.
O que você vai aprender
Neste tutorial, você vai aprender a:
- Configurar o ambiente de build
- 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 comclang
. - Confirme o resultado esperado executando
bazel build --config=clang_config //main:hello-world
em uma máquina Linux. - Criar o aplicativo C++
Antes de começar
Configurar o ambiente de build
Este tutorial pressupõe que você esteja no Linux e tenha criado
aplicativos C++ e instalado as ferramentas e bibliotecas adequadas.
O tutorial usa clang version 9.0.1
, que pode ser instalado no seu sistema.
Configure o ambiente de build da seguinte maneira:
Faça o download e instale o Bazel 0.23 ou uma versão mais recente, caso ainda não tenha feito isso.
Faça o download do exemplo de projeto C++ no GitHub e coloque-o em um diretório vazio na máquina local.
Adicione o seguinte destino
cc_binary
ao arquivomain/BUILD
:cc_binary( name = "hello-world", srcs = ["hello-world.cc"], )
Crie um arquivo
.bazelrc
na raiz do diretório do espaço de trabalho com o seguinte conteúdo para permitir o uso da flag--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 flag da linha de comando
--config={config_name}
é associada a essa flag específica. Consulte
a documentação para conferir as flags usadas:
crosstool_top
,
cpu
e
host_crosstool_top
.
Quando você cria 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. O pacote pode listar diferentes
toolchains para CPUs diferentes,
e é por isso que ele é diferenciado com a flag --cpu=k8
.
Como o Bazel usa muitas ferramentas internas escritas em C++ durante o build, como o process-wrapper, o conjunto de ferramentas C++ padrão preexistente é especificado para a plataforma de destino, para que essas ferramentas sejam criadas usando esse conjunto de ferramentas em vez de o criado neste tutorial.
Como configurar o conjunto de ferramentas C++
Para configurar a cadeia de ferramentas C++, crie o aplicativo várias vezes e elimine cada erro um por um, conforme descrito abaixo.
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 arquivoBUILD
vazio dentro do diretóriotoolchain
.Execute o build novamente. Como o pacote
toolchain
ainda não define o destinoclang_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")
Execute o build novamente. O Bazel gera o seguinte erro:
'//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
O Bazel descobriu que a flag
--crosstool_top
aponta para uma regra que não fornece o provedorToolchainInfo
necessário. Portanto, você precisa apontar--crosstool_top
para uma regra que forneçaToolchainInfo
, que é a regracc_toolchain_suite
. No arquivotoolchain/BUILD
, substitua o grupo de arquivos vazio por este:cc_toolchain_suite( name = "clang_suite", toolchains = { "k8": ":k8_toolchain", }, )
O atributo
toolchains
mapeia automaticamente os valores de--cpu
(e também de--compiler
, quando especificado) paracc_toolchain
. Você ainda não definiu nenhum destinocc_toolchain
, e o Bazel vai reclamar disso em breve.Execute o build novamente. O Bazel gera o seguinte erro:
Rule '//toolchain:k8_toolchain' does not exist
Agora, você precisa definir segmentos
cc_toolchain
para cada valor no atributocc_toolchain_suite.toolchains
. Adicione o seguinte ao arquivotoolchain/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, )
Execute o build novamente. O Bazel gera o seguinte erro:
Rule '//toolchain:k8_toolchain_config' does not exist
Em seguida, adicione um destino ":k8_toolchain_config" ao arquivo
toolchain/BUILD
:filegroup(name = "k8_toolchain_config")
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 suas cadeias de ferramentas C++. Para corrigir esse erro, crie uma regra do Starlark que forneçaCcToolchainConfigInfo
ao Bazel criando um arquivotoolchain/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 provedorCcToolchainConfigInfo
necessário. Para usar a regracc_toolchain_config
, adicione uma instrução de carga atoolchain/BUILD
logo abaixo da instrução de pacote:load(":cc_toolchain_config.bzl", "cc_toolchain_config")
E substitua o grupo de arquivos "k8_toolchain_config" por uma declaração de uma regra
cc_toolchain_config
:cc_toolchain_config(name = "k8_toolchain_config")
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ê vai 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 )
Verifique se
/usr/bin/clang
e/usr/bin/ld
são os caminhos corretos para seu sistema.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 procurar os cabeçalhos incluídos. Há várias maneiras de resolver isso, como usar o atributo
includes
decc_binary
, mas aqui isso é resolvido no nível da cadeia de ferramentas com o parâmetrocxx_builtin_include_directories
decc_common.create_cc_toolchain_config_info
. Se você estiver usando uma versão diferente declang
, 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, )
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
decc_binary
. Aqui, o problema é resolvido garantindo que qualquer destino que use a cadeia de ferramentas não precise especificar essa flag.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], )
Se você executar
bazel build --config=clang_config //main:hello-world
, ele finalmente será criado.
Revisar seu trabalho
Neste tutorial, você aprendeu a configurar um conjunto de ferramentas C++ básico, mas os conjuntos de ferramentas são mais poderosos do que este exemplo simples.
Os principais pontos são:
- É necessário especificar uma flag --crosstool_top
na linha de comando que aponte
para um cc_toolchain_suite
- É possível criar um atalho para uma configuração específica usando o arquivo
.bazelrc
- O cc_toolchain_suite pode listar cc_toolchains
para diferentes CPUs e
compiladores. É possível usar flags de linha de comando, como --cpu
, para diferenciar.
- Você precisa informar ao conjunto 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
espaços de trabalho aqui. As ferramentas podem vir de um
espaço de trabalho 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
diferentes ações, seja vinculação ou qualquer outro tipo de ação.
Leitura adicional
Para mais detalhes, consulte Configuração da cadeia de ferramentas C++.