aboutsummaryrefslogtreecommitdiff
path: root/seed/0113-bazel-cc-toolchain-api.rst
blob: 758e323501b101e11d32b8dce3e559dff9978221 (plain)
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
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
.. _seed-0113:

===========================================
0113: Add modular Bazel C/C++ toolchain API
===========================================
.. seed::
   :number: 0113
   :name: Add modular Bazel C/C++ toolchain API
   :status: Accepted
   :proposal_date: 2023-09-28
   :cl: 173453

-------
Summary
-------
This SEED proposes custom Starlark rules for declaring C/C++ toolchains in
Bazel, and scalable patterns for sharing modular components of C/C++ toolchain
definitions.

----------
Motivation
----------
There is a lot of boilerplate involved in standing up a Bazel C/C++ toolchain.
While a good portion of the verbosity of specifying a new toolchain is
important, necessary machinery, nearly as much suffers from one or more of the
following problems:

- **Underdocumented patterns**: The
  `create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_
  method has an attribute called ``target_cpu`` that doesn't have an associated
  list of expected values, which suggests the argument is for bookkeeping
  purposes and doesn't affect Bazel behavior. The reality is this argument
  *does* have expected values that change behavior, but these are undocumented
  (see `this GitHub issue <https://github.com/bazelbuild/bazel/issues/19353>`_
  for more information).

- **Not inherently modular**: Bazel expects the overwhelming majority of a
  C/C++ toolchain to be specified as part of a call to
  ``create_cc_toolchain_config_info()``. Because this is a Starlark method,
  there's a lot of flexibility with how you construct a toolchain config, but
  very little by way of existing patterns for creating something that is
  testable, sharable, or in other ways modular. The existing
  `tutorial for creating a C/C++ toolchain <https://bazel.build/tutorials/ccp-toolchain-config#configuring_the_c_toolchain>`_
  illustrates expanding out the toolchain definition as a no-argument Starlark
  rule.

As Pigweed fully embraces multi-platform builds, it is critical for both
Pigweed and users of Pigweed that it is easy to stand up custom toolchain
definitions that work for the relevant hardware project-specific code/libraries.

This SEED seeks to address the shortcomings in Bazel C/C++ toolchain
declaration by establishing patterns and providing custom build rules that
are owned and maintained by Pigweed.

--------
Proposal
--------
1. Introduce rules for defining C/C++ toolchains
================================================
In an effort to improve the experience of defining a C/C++ toolchain, Pigweed
will introduce new Bazel rules that allow toolchains to share common boilerplate
without hindering power-user functionality.

While this work centers around an improved experience for defining a toolchain
via ``create_cc_toolchain_config_info()``, other build rules will be introduced
for closely related aspects of the toolchain definition process.

An example of what these rules would look like in practice is as follows:

.. code-block::

   # A tool that can be used by various build actions.
   pw_cc_tool(
       name = "clang_tool",
       path = "@cipd_llvm_toolchain//:bin/clang",
       additional_files = [
          "@cipd_llvm_toolchain//:all",
       ],
   )

   # A mapping of a tool to actions, with flag sets that define behaviors.
   pw_cc_action_config(
       name = "clang",
       actions = ALL_ASM_ACTIONS + ALL_C_COMPILER_ACTIONS,
       tools = [
           ":clang_tool",
       ],
       flag_sets = [
           # Most flags should NOT end up here. Only unconditional flags that
           # should ALWAYS be bound to this tool (e.g. static library
           # workaround fix for macOS).
           "@pw_toolchain//flag_sets:generate_depfile",
       ],
   )

   # A trivial flag to be consumed by a C/C++ toolchain.
   pw_cc_flag_set(
       name = "werror",
       actions = ALL_COMPILE_ACTIONS,
       flags = [
           "-Werror",
       ],
   )

   # A list of flags that can be added to a toolchain configuration.
   pw_cc_flag_set(
       name = "user_compile_options",
       actions = ALL_COMPILE_ACTIONS,
       flag_groups = [
           ":user_compile_options_flags",
       ]
   )

   # A more complex compiler flag that requires template expansion.
   pw_cc_flag_group(
       name = "user_compile_options_flags",
       flags = ["%{user_compile_flags}"],
       iterate_over = "user_compile_flags",
       expand_if_available = "user_compile_flags",
   )

   # The underlying definition of a complete C/C++ toolchain.
   pw_cc_toolchain(
       name = "host_toolchain_linux",
       action_configs = [
           ":clang",
           ":clang++",
           # ...
       ],
       additional_files = ":linux_sysroot_files",
       action_config_flag_sets = [
           "@pw_toolchain//flag_sets:no_canonical_prefixes",
           ":user_compile_options",
           ":werror",
       ],
       features = [
           "@pw_toolchain//features:c++17",
       ],
       target_cpu = "x86_64",
       target_system_name = "x86_64-unknown-linux-gnu",
       toolchain_identifier = "host-toolchain-linux",
   )

   # Toolchain resolution parameters for the above C/C++ toolchain.
   toolchain(
       name = "host_cc_toolchain_linux",
       exec_compatible_with = [
           "@platforms//os:linux",
       ],
       target_compatible_with = [
           "@platforms//os:linux",
       ],
       toolchain = ":host_toolchain_linux",
       toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
   )

2. Provide standard toolchain building-blocks
=============================================
Pigweed will build out a repository of sharable instantiations of the
aforementioned custom rules to give projects the resources they need to quickly
and easily assemble toolchains for desktop and embedded targets. This includes,
but is not limited to:

- Rules that define tool sets for common toolchains (LLVM/clang, GNU/gcc).
- Fully specified, modular
  `features <https://bazel.build/docs/cc-toolchain-config-reference#features>`_.
- Common flag sets that users may want to apply directly to their toolchains.
  (enabling/disabling warnings, C++ standard version, etc.)
- Platform/architecture support rules, including host OS SDK integrations
  (Xcode, Windows SDK) and architecture-specific flag sets.

These components will help establish patterns that will make it significantly
easier for Pigweed users (and Bazel users at large) to define their own
toolchains.

---------------------
Problem investigation
---------------------
This section explores previous work, and details why existing solutions don't
meet Pigweed's needs.

bazelembedded/rules_cc_toolchain
================================
The `rules_cc_toolchain <https://github.com/bazelembedded/rules_cc_toolchain>`_
as part of the larger bazelembedded suite was actually the initial foundation
of Pigweed's Bazel build. While this served as a very good initial foundation,
it didn't provide the flexibility needed to easily stand up additional
toolchains in ways that gave downstream projects sufficient control over the
flags, libraries, tools, and sysroot.

To work around the limited configurability of toolchain flags, Pigweed employed
the following workarounds:

#. Place ``copts`` and ``linkopts`` in ``.bazelrc``: This was problematic
   because ``.bazelrc`` is not intrinsically shared with or propagated to
   downstream users of Pigweed. Also, flags here are unilaterally applied
   without OS-specific considerations.
#. Attach flags to build targets with custom wrappers: This approach
   intrinsically requires the existence of the ``pw_cc_library``, which
   introduces difficulty around consistent interoperability with other Bazel
   projects (among other issues detailed in
   `b/267498492 <https://issues.pigweed.dev/issues/267498492>`_).

Some other issues encountered when working with this solution include:

- These rules intended to be modular, but in practice were relatively tightly
  coupled.
- Transitive dependencies throughout the toolchain definition process resulted
  in some hard-to-debug issues (see
  `this pull request <https://github.com/bazelembedded/rules_cc_toolchain/pull/39>`_
  and `b/254518544 <https://issues.pigweed.dev/issues/254518544>`_.

bazelembedded/modular_cc_toolchains
===================================
The `modular_cc_toolchains <https://github.com/bazelembedded/modular_cc_toolchains>`_
repository is a new attempt as part of the bazelembedded suite at providing
truly modular toolchain rules. The proposed direction is much more in-line
with the needs of Pigweed, but at the moment the repository exists as an
initial draft of ideas rather than a complete implementation.

This repository greatly inspired Pigweed's initial prototype for modular
toolchains, but diverges significantly from the underlying Bazel C/C++
toolchain building-blocks. If this work was already complete and
well-established, it probably would have satisfied some of Pigweed's key needs.

lowRISC/crt
===========
The `compiler repository toolkit <https://github.com/lowRISC/crt>`_ is another
scalable approach at toolchains. This repository strives to be an all-in-one
repository for embedded toolchains, and does a very good job at providing
scalable models for establishing toolchains. This repository is relatively
monolithic, though, and doesn't necessarily address the concern of quickly
and easily standing up custom toolchains. Instead, it's more suited towards
contributing new one-size-fits-all toolchains to ``crt`` directly.

Android's toolchain
===================
Android's Bazel-based build has invested heavily in toolchains, but they're
very tightly coupled to the use cases of Android. For example,
`this <https://cs.android.com/android/platform/superproject/main/+/main:build/bazel/toolchains/clang/host/linux-x86/cc_toolchain_features.bzl;l=375-389;drc=097d710c349758fc2732497fe5c3d1b0a32fa4a8>`_ binds ``-fstrict-aliasing`` to a condition based on the target architecture.
These toolchains scale for the purpose of Android, but unfortunately are
inherently not modular or reusable outside of that context.

Due to the sheer amount of investment in these toolchains, though, they serve
as a good reference for building out a complete toolchain in Bazel.

Pigweed's modular Bazel toolchain prototype
===========================================
As part of an exploratory phase of getting toolchains set up for Linux and
macOS,
`an initial prototype <https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/157634>`_
for modular Bazel toolchains was drafted and deployed to Pigweed. This work
introduced two key build rules: ``pw_cc_toolchain_feature`` and
``pw_cc_toolchain``. With both of these rules, it’s possible to instantiate a
vast array of toolchain variants without writing a single line of Starlark. A
few examples of these building blocks in action are provided below.

.. code-block::

   # pw_cc_toolchain example taken from https://cs.opensource.google/pigweed/pigweed/+/main:pw_toolchain/host_clang/BUILD.bazel;l=113-143;drc=7df1768d915fe11dae05751f70f143e60acfb17a.

   pw_cc_toolchain(
       name = "host_toolchain_linux",
       abi_libc_version = "unknown",
       abi_version = "unknown",
       all_files = ":all_linux_files",
       ar = "@llvm_toolchain//:bin/llvm-ar",

       # TODO: b/305737273 - Globbing all files for every action has a
       # performance hit, make these more granular.
       ar_files = ":all_linux_files",
       as_files = ":all_linux_files",
       compiler = "unknown",
       compiler_files = ":all_linux_files",
       coverage_files = ":all_linux_files",
       cpp = "@llvm_toolchain//:bin/clang++",
       dwp_files = ":all_linux_files",
       feature_deps = [
           ":linux_sysroot",
            "@pw_toolchain//features:no_canonical_prefixes",
       ],
       gcc = "@llvm_toolchain//:bin/clang",
       gcov = "@llvm_toolchain//:bin/llvm-cov",
       host_system_name = "unknown",
       ld = "@llvm_toolchain//:bin/clang++",
       linker_files = ":all_linux_files",
       objcopy_files = ":all_linux_files",
       strip = "@llvm_toolchain//:bin/llvm-strip",
       strip_files = ":all_linux_files",
       supports_param_files = 0,
       target_cpu = "unknown",
       target_libc = "unknown",
       target_system_name = "unknown",
       toolchain_identifier = "host-toolchain-linux",
   )

   # pw_cc_toolchain_feature examples taken from https://cs.opensource.google/pigweed/pigweed/+/main:pw_toolchain_bazel/features/BUILD.bazel;l=21-34;drc=f96fd31675d136bd37a7f3840102cb256d555cea.

   # Disables linking of the default C++ standard library to allow linking of a
   # different version.
   pw_cc_toolchain_feature(
       name = "no_default_cpp_stdlib",
       linkopts = ["-nostdlib++"],
   )

   # Prevent relative paths from being converted to absolute paths.
   # Note: This initial prototype made this a feature, but it should instead
   # exist as a flag_set.
   pw_cc_toolchain_feature(
       name = "no_canonical_prefixes",
       copts = [
           "-no-canonical-prefixes",
       ],
   )

What’s worth noting is that the ``pw_cc_toolchain_feature`` build rule looks
very similar to a GN ``config``. This was no mistake, and was an attempt to
substantially reduce the boiler plate for creating new sharable compiler flag
groups.

Unfortunately, it quickly became apparent that this approach limited control
over the underlying toolchain definition creation process. In order to support
``always_link`` on macOS, a custom logic and flags had to be directly baked into
the rule used to declare toolchains
(`relevant change <https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/168614/17/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl>`_).
While workarounds like this should be possible, the fact that this had to be
upstreamed internally to ``pw_cc_toolchain`` exposed limitations in the
abstraction patterns that were established. Such limitations could preclude
some project from using ``pw_cc_toolchain`` at all.

---------------
Detailed design
---------------
The core design proposal is to transform the providers used by
``cc_common.create_cc_toolchain_config_info()`` into build rules. The approach
has been prototyped
`here <https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/168351/1>`_,
and retains API compatibility with the initial prototype as a proof-of-concept.

One core pattern established by this design is transforming content that would
typically live as Starlark to instead live in build files. This is done to
make it easier to read and reference existing work.

Implementation requirements
===========================
Compatibility with native C/C++ rules
-------------------------------------
The core of Pigweed's toolchain build rules will rely on the providers
defined as part of Bazel's
`rules_cc <https://github.com/bazelbuild/rules_cc/blob/main/cc/cc_toolchain_config_lib.bzl>`_. This means that the new rules can interop with
existing work that directly uses these toolchain primitives. It also provides
a clear path for migrating existing toolchains piece-by-piece (which may be
written completely in Starlark).

Any extensions beyond the existing providers (e.g. specifying
``additional_files`` on a ``pw_cc_tool``) must happen parallel to existing
providers so that rules that consume the ``cc_toolchain_config_lib`` providers
can work with vanilla providers.

Compatibility with Bazel rules ecosystem
----------------------------------------
In following with the larger Bazel rules ecosystem, the toolchain building
blocks will be designed such that they can be used independently from Pigweed.
This allows this work to be used for non-embedded projects, and reduces the
overhead for standing up a custom Bazel C/C++ toolchain in any arbitrary
project.

Initially, the work will live as ``pw_toolchain_bazel`` in the main Pigweed
repository to facilitate testing. This module must not depend on any other
aspects of Pigweed. As the toolchain rules mature, they will eventually be
available as a separate repository to match the modularity patterns used by
the larger Bazel rules ecosystem.

Introduce ``pw_cc_flag_set`` and ``pw_cc_flag_group``
=====================================================
The majority of build flags would be expressed as ``pw_cc_flag_set`` and
``pw_cc_flag_group`` pairs.

.. code-block::

   # A simple flag_set with a single flag.
   pw_cc_flag_set(
       name = "werror",
       # Only applies to C/C++ compile actions (i.e. no assemble/link/ar).
       actions = ALL_CPP_COMPILER_ACTIONS + ALL_C_COMPILER_ACTIONS,
       flags = [
           "-Werror",
       ],
   )

   # A flag_group that potentially expands to multiple flags.
   pw_cc_flag_group(
       name = "user_compile_options_flags",
       flags = ["%{user_compile_flags}"],
       iterate_over = "user_compile_flags",
       expand_if_available = "user_compile_flags",
   )

   # A flag_set that relies on a non-trivial or non-constant expression of
   # flags.
   pw_cc_flag_set(
       name = "user_compile_options",
       actions = ALL_COMPILE_ACTIONS,
       flag_groups = [
           ":user_compile_options_flags",
       ]
   )

These closely mimic the API of ``cc_toolchain_config_lib.flag_set()`` and
``cc_toolchain_config_lib.flag_group()``, with the following exceptions:

**pw_cc_flag_set**

*Added*

- ``flags`` (added): Express a constant, trivial list of flags. If this is
  specified, ``flag_groups`` may not be specified. This eliminates the need
  for specifying a corresponding ``pw_cc_flag_group`` for every
  ``pw_cc_flag_set`` for most flags.

**pw_cc_flag_group**

*Removed*

- ``expand_if_true``\, ``expand_if_false``\, ``expand_if_equal``\: More complex
  rules that rely on these should live as custom Starlark rules that provide a
  ``FlagGroupInfo``\, or ``FlagSetInfo`` (depending on which is more ergonomic
  to express the intent). See :ref:`pw_cc_flag_set-exceptions` below for an
  example that illustrates how express more complex ``flag_group``\s that rely
  on these attributes.

Application of flags
--------------------
Flags can be applied to a toolchain in three ways. This section attempts to
provide initial guidance for where flags should be applied, though it's likely
better practices will evolve as this work sees more use. For the latest
guidance, please consult the official documentation when it rolls out to
``pw_toolchain_bazel``.

Flags unconditionally applied to a toolchain
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The majority of flags fall into this category. Architecture flags,
globally-applied warnings, global defines, and other similar flags should be
applied in the ``action_config_flag_sets`` attribute of a ``pw_cc_toolchain``
(see :ref:`pw_cc_toolchain-toolchain-declarations` for more information). Each
``pw_cc_flag_set`` (or other rule that provides a ``FlagSetInfo`` provider)
listed in ``action_config_flag_sets`` is unconditionally applied to every tool
that matches the ``actions`` listed in the flag set.

.. _application-of-flags-feature-flags:

Feature flags
~~~~~~~~~~~~~
Flag sets applied as features may or may not be enabled even if they are listed
in the ``features`` attribute of a ``pw_cc_toolchain``. The
`official Bazel documentation on features <https://bazel.build/docs/cc-toolchain-config-reference#features>`_
provides some good guidance on when features should be employed. To summarize,
features should be used when either they should be controllable by users
invoking the build, or if they affect build behavior beyond simply
adding/removing flags (e.g. by introducing additional build actions).

Flags unconditionally applied to a tool
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These flags are flags that are bound to a particular tool. These are not
expressed as part of a ``pw_cc_toolchain``, and are instead bound to a
``pw_cc_action_config``. This means that the flag set is unconditionally
applied to every user of that action config. These kinds of flag applications
should be reserved for flags required to assemble a working set of tools (such
as generating a depfile, or adding support for static library link handling
:ref:`as illustrated below <pw_cc_flag_set-exceptions>`).

Flag application order
~~~~~~~~~~~~~~~~~~~~~~
When invoking the underlying tools, the intended order of flags is as follows:

#. Flags listed in the ``flag_sets`` list of a ``pw_cc_action_config``.
#. Flags listed in ``action_config_flag_sets`` of a ``pw_cc_toolchain``.
#. Flags listed in ``features`` of a ``pw_cc_toolchain``.

These lists are intended to be sensitive to ordering, earlier items in the lists
should appear in the final tool invocation flags before later items in the list.

As transitive dependencies between features/flags are not supported as part of
this proposal, exact traversal of transitive flag dependencies will be left
to be decided if/when that feature is introduced. This proposal suggests
postorder handling of flags as the most intuitive order.

.. _pw_cc_flag_set-exceptions:

Exceptions
----------
Some flags are too complex to be nicely expressed in a Bazel build file. These
flag sets or flag groups will need to be expressed in Starlark as custom rules.
Fortunately, this will interop well with simpler flag sets since the underlying
providers are all the same.

**Example**

In a Starlark file (e.g. ``//tools/llvm/llvm_ar_patch.bzl``), the required
``flag_set`` can be defined:

.. code-block::

   # Starlark rules in a .bzl file for a relatively complicated workaround for
   # what would normally be inherently managed by Bazel internally.
   # TODO: b/297413805 - Remove this implementation.

   def _pw_cc_static_libs_to_link_impl():
      """Returns a flag_set provider that sets up static libraries to link."""
      return flag_set(
               actions = [
                   ACTION_NAMES.cpp_link_static_library,
               ],
               flag_groups = [
                   flag_group(
                       expand_if_available = "libraries_to_link",
                       iterate_over = "libraries_to_link",
                       flag_groups = [
                           flag_group(
                               expand_if_equal = variable_with_value(
                                   name = "libraries_to_link.type",
                                   value = "object_file",
                               ),
                               flags = ["%{libraries_to_link.name}"],
                           ),
                           flag_group(
                               expand_if_equal = variable_with_value(
                                   name = "libraries_to_link.type",
                                   value = "object_file_group",
                               ),
                               flags = ["%{libraries_to_link.object_files}"],
                               iterate_over = "libraries_to_link.object_files",
                           ),
                       ],
                   ),
               ],
           )

   pw_cc_static_libs_to_link = rule(
       implementation = _pw_cc_static_libs_to_link_impl,
       provides = [FlagSetInfo],
   )

And then in the ``BUILD.bazel`` file, the rules would be used as if they
were a ``pw_cc_flag_set``:

.. code-block::

   load(
       "@pw_toolchain//tools/llvm:llvm_ar_patch.bzl",
       "pw_cc_static_libs_to_link"
   )

   pw_cc_static_libs_to_link(
       name = "static_library_action_flags",
   )

   pw_cc_action_config(
       name = "llvm_ar",
       actions = ACTION_NAMES.cpp_link_static_library,
       tools = [
           ":llvm_ar_tool",
       ],
       flag_sets = [
           ":static_library_action_flags",
       ],
   )

Introduce ``pw_cc_feature`` and ``pw_cc_feature_set``
=====================================================
These types are just permutations of the ``cc_toolchain_config_lib.feature()``
and ``cc_toolchain_config_lib.with_feature_set()`` API. For guidance on when
these should be used, see
:ref:`application of feature flags <application-of-flags-feature-flags>`.

.. code-block::

   pw_cc_feature_set(
       name = "static_pie_requirements",
       with_features = ["pie"],
       # If this doesn't work when certain features are enabled, they should
       # be specified as ``without_features``.
   )

   pw_cc_feature(
       name = "static_pie",
       flag_sets = [
           "//flag_sets:static_pie",
       ],
       implies = ["static_link_flag"],
       requires = [
           ":static_pie_requirements",
       ],
   )

Introduce ``pw_cc_action_config`` and ``pw_cc_tool``
====================================================
These are closely related to the ``ActionConfigInfo`` and ``ToolInfo``
providers, but allow additional files to be attached and a list of actions to
be attached rather than a single action.

.. code-block::

   pw_cc_tool(
       name = "clang_tool",
       path = "@llvm_toolchain//:bin/clang",
       additional_files = [
           "@llvm_toolchain//:all",
       ],
   )

   pw_cc_action_config(
       name = "clang",
       actions = ALL_ASM_ACTIONS + ALL_C_COMPILER_ACTIONS,
       tools = [
           ":clang_tool",
       ],
       flag_sets = [
           # Most flags should NOT end up here. Only unconditional flags that
           # should ALWAYS be bound to this tool (e.g. static library
           # workaround fix for macOS).
           "//flag_sets:generate_depfile",
       ],
   )

.. _pw_cc_toolchain-toolchain-declarations:

Toolchain declarations
======================
In following with the other proposed rules, ``pw_cc_toolchain`` largely
follows the API of ``cc_common.create_cc_toolchain_config_info()``. Most of the
attributes are logically passed through, with the following exceptions:

- **action_config_flag_sets**: Flag sets to apply to action configs. Since flag
  sets are intrinsically bound to actions, there’s no need to divide them at
  this level.
- **additional_files**: Now that tools can spec out required files, those
  should be propagated and mostly managed internally. The ``\*_files`` members
  will still be available, but shouldn’t see much use. additional_files is like
  “all_files”, but applies to all action_configs.

.. code-block::

   pw_cc_toolchain(
       name = "host_toolchain_linux",
       abi_libc_version = "unknown",  # We should consider how to move this out in the future.
       abi_version = "unknown",
       action_configs = [
           "@llvm_toolchain//tools:clang",
           "@llvm_toolchain//tools:clang++",
           "@llvm_toolchain//tools:lld",
           "@llvm_toolchain//tools:llvm_ar",
           "@llvm_toolchain//tools:llvm_cov",
           "@llvm_toolchain//tools:llvm_strip",
       ],
       additional_files = ":linux_sysroot_files",
       action_config_flag_sets = [
           ":linux_sysroot",
           "@pw_toolchain//flag_collections:strict_warnings",
           "@pw_toolchain//flag_sets:no_canonical_prefixes",
       ],
       features = [
           "@pw_toolchain//features:c++17",
       ],
       host_system_name = "unknown",
       supports_param_files = 0,  # Seems like this should be attached to a pw_cc_action_config...
       target_cpu = "unknown",
       target_libc = "unknown",
       target_system_name = "unknown",
       toolchain_identifier = "host-toolchain-linux",
       cxx_builtin_include_directories = [
           "%package(@llvm_toolchain//)%/include/x86_64-unknown-linux-gnu/c++/v1",
           "%package(@llvm_toolchain//)%/include/c++/v1",
           "%package(@llvm_toolchain//)%/lib/clang/17/include",
           "%sysroot%/usr/local/include",
           "%sysroot%/usr/include/x86_64-linux-gnu",
           "%sysroot%/usr/include",
       ],
   )

------------
Alternatives
------------
Improve Bazel's native C/C++ toolchain rules
============================================
Improving Bazel's native rules for defining C/C++ toolchains is out of the
scope of Pigweed's work. Changing the underlying toolchain API as Bazel
understands it is a massive undertaking from the perspective of migrating
existing code. We hope that the custom rule effort can help guide future
decisions when it comes to toolchain scalability and maintainability.

----------
Next steps
----------
Rust toolchain interop
======================
Pigweed's Rust toolchains have some interoperability concerns and requirements.
The extend of this needs to be thoroughly investigated as a next step to ensure
that the Rust/C/C++ toolchain experience is relatively unified and ergonomic.

More maintainable ``cxx_builtin_include_directories``
=====================================================
In the future, it would be nice to have a more sharable solution for managing
``cxx_builtin_include_directories`` on a ``pw_cc_toolchain``. This could
plausibly be done by allowing ``pw_cc_flag_set`` to express
``cxx_builtin_include_directories`` so they can be propagated back up to the
``pw_cc_toolchain``.

Feature name collision guidance
===============================
Features support relatively complex relationships among each other, but
traditionally rely on string names to express these relationships rather than
labels. This introduces significant ambiguity, as it's possible for multiple
features to use the same logical name so long as they aren't both employed in
the same toolchain. In practice, the only way to tell what features will end up
enabled is to manually unpack what features a toolchain pulls in, and
cross-reference it against the output of
`--experimental_save_feature_state <https://bazel.build/reference/command-line-reference#flag--experimental_save_feature_state>`_.

One potential solution to this problem is to add a mechanism for expressing
features as labels, which will allow relationships to be expressed more
concretely, and help prevent unintended naming collisions. This would not
replace the ability to express relationships with features not accessible via
labels, but rather live alongside it.