Name Date Size #Lines LOC

..--

actions/H25-Apr-2025-285243

features/H25-Apr-2025-379321

impl/H25-Apr-2025-1,4281,247

variables/H25-Apr-2025-482414

BUILDH A D25-Apr-2025812 2219

README.mdH A D25-Apr-20259.7 KiB334273

action_type_config.bzlH A D25-Apr-20254.4 KiB138121

actions.bzlH A D25-Apr-20252.3 KiB8472

args.bzlH A D25-Apr-20253.5 KiB121107

args_list.bzlH A D25-Apr-20251.1 KiB3632

cc_toolchain_info.bzlH A D25-Apr-20259.2 KiB192176

feature.bzlH A D25-Apr-20258.3 KiB244211

feature_constraint.bzlH A D25-Apr-20251.8 KiB5550

feature_set.bzlH A D25-Apr-20251.6 KiB5852

format.bzlH A D25-Apr-20251.1 KiB2724

mutually_exclusive_category.bzlH A D25-Apr-20251.1 KiB3026

nested_args.bzlH A D25-Apr-20251.3 KiB4639

tool.bzlH A D25-Apr-20253.3 KiB10797

toolchain.bzlH A D25-Apr-20255.7 KiB144133

README.md

1# Writing a custom rule_based C++ toolchain with rule-based definition.
2
3Work in progress!
4
5This document serves two purposes:
6* Until complete, this serves as an agreement for the final user-facing API.
7* Once complete, this will serve as onboarding documentation.
8
9This section will be removed once complete.
10
11## Step 1: Define tools
12A tool is simply a binary. Just like any other bazel binary, a tool can specify
13additional files required to run.
14
15We can use any bazel binary as an input to anything that requires tools. In the
16example below, you could use both clang and ld as tools.
17
18```
19# @sysroot//:BUILD
20cc_tool(
21    name = "clang",
22    exe = ":bin/clang",
23    execution_requirements = ["requires-mem:24g"],
24    data = [...],
25)
26
27sh_binary(
28    name = "ld",
29    srcs = ["ld_wrapper.sh"],
30    data = [":bin/ld"],
31)
32
33```
34
35## Step 2: Generate action configs from those tools
36An action config is a mapping from action to:
37
38* A list of tools, (the first one matching the execution requirements is used).
39* A list of args and features that are always enabled for the action
40* A set of additional files required for the action
41
42Each action can only be specified once in the toolchain. Specifying multiple
43actions in a single `cc_action_type_config` is just a shorthand for specifying the
44same config for every one of those actions.
45
46If you're already familiar with how to define toolchains, the additional files
47is a replacement for `compile_files`, `link_files`, etc.
48
49Additionally, to replace `all_files`, we add `cc_additional_files_for_actions`.
50This allows you to specify that particular files are required for particular
51actions.
52
53We provide `additional_files` on the `cc_action_type_config` as a shorthand for
54specifying `cc_additional_files_for_actions`
55
56Warning: Implying a feature that is not listed directly in the toolchain will throw
57an error. This is to ensure you don't accidentally add a feature to the
58toolchain.
59
60```
61cc_action_type_config(
62    name  = "c_compile",
63    actions = ["@rules_cc//actions:all_c_compile"],
64    tools = ["@sysroot//:clang"],
65    args = [":my_args"],
66    implies = [":my_feature"],
67    additional_files = ["@sysroot//:all_header_files"],
68)
69
70cc_additional_files_for_actions(
71    name = "all_action_files",
72    actions = ["@rules_cc//actions:all_actions"],
73    additional_files = ["@sysroot//:always_needed_files"]
74)
75```
76
77## Step 3: Define some arguments
78Arguments are our replacement for `flag_set` and `env_set`. To add arguments to
79our tools, we take heavy inspiration from bazel's
80[`Args`](https://bazel.build/rules/lib/builtins/Args) type. We provide the same
81API, with the following caveats:
82* `actions` specifies which actions the arguments apply to (same as `flag_set`).
83* `requires_any_of` is equivalent to `with_features` on the `flag_set`.
84* `args` may be used instead of `add` if your command-line is only strings.
85* `env` may be used to add environment variables to the arguments. Environment
86  variables set by later args take priority.
87* By default, all inputs are automatically added to the corresponding actions.
88  `additional_files` specifies files that are required for an action when using
89  that argument.
90
91```
92cc_args(
93    name = "inline",
94    actions = ["@rules_cc//actions:all_cpp_compile_actions"],
95    args = ["--foo"],
96    requires_any_of = [":feature"]
97    env = {"FOO": "bar"},
98    additional_files = [":file"],
99)
100```
101
102For more complex use cases, we use the same API as `Args`. Values are either:
103* A list of files (or a single file for `cc_add_args`).
104* Something returning `CcVariableInfo`, which is equivalent to a list of strings.
105
106```
107cc_variable(
108  name = "bar_baz",
109  values = ["bar", "baz"],
110)
111
112# Expands to CcVariableInfo(values = ["x86_64-unknown-linux-gnu"])
113custom_variable_rule(
114  name = "triple",
115  ...
116)
117
118# Taken from https://bazel.build/rules/lib/builtins/Args#add
119cc_add_args(
120    name = "single",
121    arg_name = "--platform",
122    value = ":triple", # Either a single file or a cc_variable
123    format = "%s",
124)
125
126# Taken from https://bazel.build/rules/lib/builtins/Args#add_all
127cc_add_args_all(
128    name = "multiple",
129    arg_name = "--foo",
130    values = [":file", ":file_set"], # Either files or cc_variable.
131    # map_each not supported. Write a custom rule if you want that.
132    format_each = "%s",
133    before_each = "--foo",
134    omit_if_empty = True,
135    uniquify = False,
136    # Expand_directories not yet supported.
137    terminate_with = "foo",
138)
139
140# Taken from https://bazel.build/rules/lib/builtins/Args#add_joined
141cc_add_args_joined(
142    name = "joined",
143    arg_name = "--foo",
144    values = [":file", ":file_set"], # Either files or cc_variable.
145    join_with = ",",
146    # map_each not supported. Write a custom rule if you want that.
147    format_each = "%s",
148    format_joined = "--foo=%s",
149    omit_if_empty = True,
150    uniquify = False,
151    # Expand_directories not yet supported.
152)
153
154cc_args(
155    name = "complex",
156    actions = ["@rules_cc//actions:c_compile"],
157    add = [":single", ":multiple", ":joined"],
158)
159
160cc_args_list(
161    name = "all_flags",
162    args = [":inline", ":complex"],
163)
164```
165
166## Step 4: Define some features
167A feature is a set of args and configurations that can be enabled or disabled.
168
169Although the existing toolchain recommends using features to avoid duplication
170of definitions, we recommend avoiding using features unless you want the user to
171be able to enable / disable the feature themselves. This is because we provide
172alternatives such as `cc_args_list` to allow combining arguments and
173specifying them on each action in the action config.
174
175```
176cc_feature(
177    name = "my_feature",
178    feature_name = "my_feature",
179    args = [":all_args"],
180    implies = [":other_feature"],
181)
182```
183
184## Step 5: Generate the toolchain
185The `cc_toolchain` macro:
186
187* Performs validation on the inputs (eg. no two action configs for a single
188  action)
189* Converts the type-safe providers to the unsafe ones in
190  `cc_toolchain_config_lib.bzl`
191* Generates a set of providers for each of the filegroups respectively
192* Generates the appropriate `native.cc_toolchain` invocation.
193
194```
195cc_toolchain(
196    name = "toolchain",
197    features = [":my_feature"]
198    unconditional_args = [":all_warnings"],
199    action_type_configs = [":c_compile"],
200    additional_files = [":all_action_files"],
201)
202```
203
204# Ancillary components for type-safe toolchains.
205## Well-known features
206Well-known features will be defined in `@rules_cc//features/well_known:*`.
207Any feature with `feature_name` in the well known features will have to specify
208overrides.
209
210`cc_toolchain` is aware of the builtin / well-known features. In order to
211ensure that a user understands that this overrides the builtin opt feature (I
212originally thought that it added extra flags to opt, but you still got the
213default ones, so that can definitely happen), and to ensure that they don't
214accidentally do so, we will force them to explicitly specify that it overrides
215the builtin one. This is essentially just an acknowledgement of "I know what
216I'm doing".
217
218Warning: Specifying two features with the same name is an error, unless one
219overrides the other.
220
221```
222cc_feature(
223    name = "opt",
224    ...,
225    overrides = "@rules_cc//features/well_known:opt",
226)
227```
228
229In addition to well-known features, we could also consider in future iterations
230to also use known features for partial migrations, where you still imply a
231feature that's still defined by the legacy API:
232
233```
234# Implementation
235def cc_legacy_features(name, features):
236  for feature in features:
237    cc_known_feature(name = name + "_" + feature.name)
238  cc_legacy_features(name = name, features = FEATURES)
239
240
241# Build file
242FOO = feature(name = "foo", args=[arg_group(...)])
243FEATURES = [FOO]
244cc_legacy_features(name = "legacy_features", features = FEATURES)
245
246cc_feature(name = "bar", implies = [":legacy_features_foo"])
247
248cc_toolchain(
249  name = "toolchain",
250  legacy_features = ":legacy_features",
251  features = [":bar"],
252)
253```
254
255## Mutual exclusion
256Features can be mutually exclusive.
257
258We allow two approaches to mutual exclusion - via features or via categories.
259
260The existing toolchain uses `provides` for both of these. We rename it so that
261it makes more sense semantically.
262
263```
264cc_feature(
265   name = "incompatible_with_my_feature",
266   feature_name = "bar",
267   mutually_exclusive = [":my_feature"],
268)
269
270
271# This is an example of how we would define compilation mode.
272# Since it already exists, this wouldn't work.
273cc_mutual_exclusion_category(
274    name = "compilation_mode",
275)
276
277cc_feature(
278    name = "opt",
279    ...
280    mutually_exclusive = [":compilation_mode"],
281)
282cc_feature(
283    name = "dbg",
284    ...
285    mutually_exclusive = [":compilation_mode"],
286)
287```
288
289## Feature requirements
290Feature requirements can come in two formats.
291
292For example:
293
294* Features can require some subset of features to be enabled.
295* Arguments can require some subset of features to be enabled, but others to be
296  disabled.
297
298This is very confusing for toolchain authors, so we will simplify things with
299the use of providers:
300
301* `cc_feature` will provide `feature`, `feature_set`, and `with_feature`
302* `cc_feature_set` will provide `feature_set` and `with_feature`.
303* `cc_feature_constraint` will provide `with_features` only.
304
305We will rename all `with_features` and `requires` to `requires_any_of`, to make
306it very clear that only one of the requirements needs to be met.
307
308```
309cc_feature_set(
310    name = "my_feature_set",
311    all_of = [":my_feature"],
312)
313
314cc_feature_constraint(
315    name = "my_feature_constraint",
316    all_of = [":my_feature"],
317    none_of = [":my_other_feature"],
318)
319
320cc_args(
321   name = "foo",
322   # All of these provide with_feature.
323   requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"]
324)
325
326# my_feature_constraint would be an error here.
327cc_feature(
328   name = "foo",
329   # Both of these provide feature_set.
330   requires_any_of = [":my_feature", ":my_feature_set"]
331   implies = [":my_other_feature", :my_other_feature_set"],
332)
333```
334