File: cc-toolchain-config.md

package info (click to toggle)
bazel-bootstrap 4.2.3%2Bds-9
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 85,476 kB
  • sloc: java: 721,710; sh: 55,859; cpp: 35,359; python: 12,139; xml: 295; objc: 269; makefile: 113; ansic: 106; ruby: 3
file content (483 lines) | stat: -rwxr-xr-x 16,987 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
---
layout: documentation
title: Configuring C++ toolchains
---

# Configuring C++ toolchains

## Overview

This tutorial uses an example scenario to describe how to configure C++
toolchains for a project. It's based on an
[example C++ project](https://github.com/bazelbuild/examples/tree/master/cpp-tutorial/stage1)
that builds error-free using `clang`.

In this tutorial, you will create a Starlark rule that provides additional
configuration for the `cc_toolchain` so that Bazel can build the application
with `clang`. The expected outcome is to run
`bazel build --config=clang_config //main:hello-world` on a Linux machine and
build the C++ application. For additional details please visit
[C++ toolchain configuration](../cc-toolchain-config-reference.html)

## Setting up the build environment

This tutorial assumes you are on Linux on which you have successfully built
C++ applications - in other words, we assume that appropriate tooling and
libraries have been installed. The tutorial uses `clang version 9.0.1` which you
can install on your system.

Set up your build environment as follows:

1.  If you have not already done so,
   [download and install Bazel 0.23](../install-ubuntu.html) or later.

2.  Download the
    [example C++ project](https://github.com/bazelbuild/examples/tree/master/cpp-tutorial/stage1)
    from GitHub and place it in an empty directory on your local machine.


3.  Add the following `cc_binary` target to the `main/BUILD` file:

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

4.  Create a `.bazelrc` file at the root of the workspace directory with the
    following contents to enable the use of the `--config` flag:

    ```
    # 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
    ```

For an entry `build:{config_name} --flag=value`, the command line flag
`--config={config_name}` will be associated with that particular flag. See
documentation for the flags used:
[crosstool_top](../user-manual.html#flag--crosstool_top),
[cpu](../user-manual.html#flag--cpu) and
[host_crosstool_top](../user-manual.html#flag--host_crosstool_top).

What this means is that when we build our [target](../build-ref.html#targets)
with `bazel build --config=clang_config //main:hello-world` Bazel will use our
custom toolchain from the
[cc_toolchain_suite](../be/c-cpp.html#cc_toolchain_suite)
`//toolchain:clang_suite`. The suite may list different [toolchains](../be/c-cpp.html#cc_toolchain)
for different CPUs, that's why we differentiate with the flag `--cpu=k8`.

Since Bazel uses many internal tools written in
C++ during the build, such as process-wrapper, we are specifying the
pre-existing default C++ toolchain for the host platform, so that these tools
are built using that toolchain instead of the one created in this tutorial.

## Configuring the C++ toolchain

To configure the C++ toolchain, repeatedly build the application and eliminate
each error one by one as described below.

**Note:** This tutorial assumes you're using Bazel 0.23 or later. If you're
using an older release of Bazel, look for the "Configuring CROSSTOOL" tutorial.
It also assumes `clang version 9.0.1`, although the details should only change
slightly between different versions of clang.

1.  Run the build with the following command:

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

    Because you specified `--crosstool_top=//toolchain:clang_suite` in the
    `.bazelrc` file, Bazel throws the following error:

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

    In the workspace directory, create the `toolchain` directory for the package
    and an empty `BUILD` file inside the `toolchain` directory.

2.  Run the build again. Because the `toolchain` package does not yet define the
    `clang_suite` target, Bazel throws the following error:

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

    In the `toolchain/BUILD` file, define an empty filegroup as follows:

    ```python
    package(default_visibility = ["//visibility:public"])

    filegroup(name = "clang_suite")
    ```

3.  Run the build again. Bazel throws the following error:

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

    Bazel discovered that the `--crosstool_top` flag points to a rule that
    doesn't provide the necessary [`ToolchainInfo`](../skylark/lib/ToolchainInfo.html)
    provider. So we need to point `--crosstool_top` to a rule that does provide
    `ToolchainInfo` - that is the `cc_toolchain_suite` rule. In the
    `toolchain/BUILD` file, replace the empty filegroup with the following:

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

    The `toolchains` attribute automatically maps the `--cpu` (and also
    `--compiler` when specified) values to  `cc_toolchain`. You have not yet
    defined any `cc_toolchain` targets and Bazel will complain about that
    shortly.

4.  Run the build again. Bazel throws the following error:

    ```
    Rule '//toolchain:k8_toolchain' does not exist
    ```

    Now you need to define `cc_toolchain` targets for every value in the
    `cc_toolchain_suite.toolchains` attribute. Add the following to the
    `toolchain/BUILD` file:

    ```python
    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.  Run the build again. Bazel throws the following error:

    ```
    Rule '//toolchain:k8_toolchain_config' does not exist
    ```

    Let's add a ":k8_toolchain_config" target to the `toolchain/BUILD` file:

    ```python
    filegroup(name = "k8_toolchain_config")
    ```

6.  Run the build again. Bazel throws the following error:

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

    `CcToolchainConfigInfo` is a provider that we use to configure our C++
    toolchains. We are going to create a Starlark rule that will provide
    `CcToolchainConfigInfo`. Create a `toolchain/cc_toolchain_config.bzl`
    file with the following content:

    ```python
    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()` creates the needed provider
    `CcToolchainConfigInfo`. Now let's declare a rule that will make use of
    the newly implemented `cc_toolchain_config` rule. Add a load statement to
    `toolchains/BUILD`:

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

    And replace the "k8_toolchain_config" filegroup with a declaration of a
    `cc_toolchain_config` rule:

    ```python
    cc_toolchain_config(name = "k8_toolchain_config")
    ```

7.  Run the build again. Bazel throws the following error:

    ```
    .../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`
    ```

    At this point, Bazel has enough information to attempt building the code but
    it still does not know what tools to use to complete the required build
    actions. We will modify our Starlark rule implementation to tell Bazel what
    tools to use. For that, we'll need the tool_path() constructor from
    [`@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl`](https://source.bazel.build/bazel/+/4eea5c62a566d21832c93e4c18ec559e75d5c1ce:tools/cpp/cc_toolchain_config_lib.bzl;l=400):

    ```python
    # 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
        )
    ```

    Make sure that `/usr/bin/clang` and `/usr/bin/ld` are the correct paths
    for your system.

8.  Run the build again. Bazel throws the following error:
     ```
     ..../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'
     ....
     ```
     Bazel needs to know where to search for included headers. There are
     multiple ways to solve this like using the `includes` attribute of
     `cc_binary`, but here we will solve it at the toolchain level with the
     [`cxx_builtin_include_directories`](../skylark/lib/cc_common.html#create_cc_toolchain_config_info)
     parameter of `cc_common.create_cc_toolchain_config_info`. Beware that if
     you are using a different version of `clang`, the include path will be
     different. These paths may also be different depending on the distribution.

     Modify the return value in `toolchain/cc_toolchain_config.bzl` to look
     like this:

     ```python
     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. Run the build command again, you will see an error like:
    ```
    /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'
    ```
    The reason for this is because the linker is missing the C++ standard library
    and it can't find its symbols. There are many ways to solve this, like using
    the `linkopts` attribute of `cc_binary`. Here we will solve it making sure
    that any target using our toolchain doesn't have to specify this flag. Copy
    the following code to `cc_toolchain_config.bzl`.

     ```python
      # 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. If you run `bazel build --config=clang_config //main:hello-world`, it should
    finally build.

In this tutorial you have learned how to configure a basic C++ toolchain. But
toolchains are much more powerful than this simple example. You can visit
[C++ toolchain configuration](../cc-toolchain-config-reference.html)
to learn more about them.

The key take-aways are:
- You need to specify a `--crosstool_top` flag in the command line which should
  point to a `cc_toolchain_suite`
- You can create a shortcut for a particular configuration using the `.bazelrc`
  file
- The cc_toolchain_suite may list `cc_toolchains` for different CPUs and
  compilers. You can use command line flags like `--cpu` to differentiate.
- You have to let the toolchain know where the tools live. In this tutorial
  we have a simplified version where we access the tools from the system. If you
  are interested in a more self-contained approach you can read about workspaces
  [here](../be/workspace.html). Your tools
  could come from a different workspace and you would have to make their files
  available to the `cc_toolchain` via target dependencies on attributes like
  `compiler_files`. The `tool_paths` would need to be changed as well.
- You can create features to customize which flags should be passed to different
  actions, be it linking or any other type of action.