xref: /aosp_15_r20/external/pigweed/pw_toolchain_bazel/cc_toolchain/private/utils.bzl (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2023 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Private utilities and global variables."""
15
16load(
17    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
18    rules_cc_action_config = "action_config",
19    rules_cc_env_entry = "env_entry",
20    rules_cc_env_set = "env_set",
21    rules_cc_feature = "feature",
22    rules_cc_feature_set = "feature_set",
23    rules_cc_flag_set = "flag_set",
24    rules_cc_with_feature_set = "with_feature_set",
25)
26
27visibility(["//cc_toolchain/tests/..."])
28
29ALL_FILE_GROUPS = {
30    "ar_files": ["@pw_toolchain//actions:all_ar_actions"],
31    "as_files": ["@pw_toolchain//actions:all_asm_actions"],
32    "compiler_files": ["@pw_toolchain//actions:all_compiler_actions"],
33    "coverage_files": ["@pw_toolchain//actions:llvm_cov"],
34    "dwp_files": [],
35    "linker_files": ["@pw_toolchain//actions:all_link_actions"],
36    "objcopy_files": ["@pw_toolchain//actions:objcopy_embed_data"],
37    "strip_files": ["@pw_toolchain//actions:strip"],
38}
39
40def _ensure_fulfillable(any_of, known, label, fail = fail):
41    # Requirements can be fulfilled if there are no requirements.
42    fulfillable = not any_of
43    for group in any_of:
44        all_met = True
45        for entry in group.to_list():
46            if entry.label not in known:
47                all_met = False
48                break
49        if all_met:
50            fulfillable = True
51            break
52
53    if not fulfillable:
54        fail("%s cannot possibly be enabled (none of the constraints it requires fully exist). Either remove it from your toolchain, or add the requirements." % label)
55
56def _to_untyped_flag_set(flag_set, known, fail = fail):
57    """Converts a PwFlagSet to rules_cc's flag set."""
58    _ensure_fulfillable(
59        any_of = [constraint.all_of for constraint in flag_set.requires_any_of],
60        known = known,
61        label = flag_set.label,
62        fail = fail,
63    )
64    actions = list(flag_set.actions)
65    with_features = [
66        _to_untyped_feature_constraint(fc)
67        for fc in flag_set.requires_any_of
68    ]
69
70    out_flag_set = None
71    if flag_set.flag_groups:
72        out_flag_set = rules_cc_flag_set(
73            flag_groups = list(flag_set.flag_groups),
74            actions = actions,
75            with_features = with_features,
76        )
77
78    out_env_set = None
79    if flag_set.env:
80        out_env_set = rules_cc_env_set(
81            env_entries = [
82                rules_cc_env_entry(
83                    key = key,
84                    value = value,
85                    expand_if_available = flag_set.env_expand_if_available,
86                )
87                for key, value in flag_set.env.items()
88            ],
89            actions = actions,
90            with_features = with_features,
91        )
92    return struct(
93        flag_set = out_flag_set,
94        env_set = out_env_set,
95    )
96
97def _to_untyped_flag_sets(flag_sets, known, fail):
98    out_flag_sets = []
99    out_env_sets = []
100    out = [_to_untyped_flag_set(flag_set, known, fail) for flag_set in flag_sets]
101    for entry in out:
102        if entry.flag_set != None:
103            out_flag_sets.append(entry.flag_set)
104        if entry.env_set != None:
105            out_env_sets.append(entry.env_set)
106    return struct(flag_sets = out_flag_sets, env_sets = out_env_sets)
107
108def _to_untyped_feature_set(feature_set):
109    return rules_cc_feature_set([
110        feature.name
111        for feature in feature_set.features.to_list()
112    ])
113
114def _to_untyped_feature_constraint(feature_constraint):
115    return rules_cc_with_feature_set(
116        features = [ft.name for ft in feature_constraint.all_of.to_list()],
117        not_features = [ft.name for ft in feature_constraint.none_of.to_list()],
118    )
119
120def _to_untyped_implies(provider, known, fail = fail):
121    implies = []
122    for feature in provider.implies_features.to_list():
123        if feature.label not in known:
124            fail("%s implies %s, which is not explicitly mentioned in your toolchain configuration" % (provider.label, feature.label))
125        implies.append(feature.name)
126    for action_config in provider.implies_action_configs.to_list():
127        if action_config.label not in known:
128            fail("%s implies %s, which is not explicitly mentioned in your toolchain configuration" % (provider.label, action_config.label))
129        implies.append(action_config.action_name)
130    return implies
131
132def _to_untyped_feature(feature, known, fail = fail):
133    if feature.known:
134        return None
135
136    _ensure_fulfillable(
137        any_of = [fs.features for fs in feature.requires_any_of],
138        known = known,
139        label = feature.label,
140        fail = fail,
141    )
142
143    flags = _to_untyped_flag_sets(feature.flag_sets.to_list(), known, fail = fail)
144
145    return rules_cc_feature(
146        name = feature.name,
147        enabled = feature.enabled,
148        flag_sets = flags.flag_sets,
149        env_sets = flags.env_sets,
150        implies = _to_untyped_implies(feature, known, fail = fail),
151        requires = [_to_untyped_feature_set(requirement) for requirement in feature.requires_any_of],
152        provides = list(feature.provides),
153    )
154
155def _to_untyped_tool(tool, known, fail = fail):
156    _ensure_fulfillable(
157        any_of = [constraint.all_of for constraint in tool.requires_any_of],
158        known = known,
159        label = tool.label,
160        fail = fail,
161    )
162
163    # Rules_cc is missing the "tool" parameter.
164    return struct(
165        path = tool.path,
166        tool = tool.exe,
167        execution_requirements = list(tool.execution_requirements),
168        with_features = [
169            _to_untyped_feature_constraint(fc)
170            for fc in tool.requires_any_of
171        ],
172        type_name = "tool",
173    )
174
175def _to_untyped_action_config(action_config, extra_flag_sets, known, fail = fail):
176    # De-dupe, in case the same flag set was specified for both unconditional
177    # and for a specific action config.
178    flag_sets = depset(
179        list(action_config.flag_sets) + extra_flag_sets,
180        order = "preorder",
181    ).to_list()
182    untyped_flags = _to_untyped_flag_sets(flag_sets, known = known, fail = fail)
183    implies = _to_untyped_implies(action_config, known, fail = fail)
184
185    # Action configs don't take in an env like they do a flag set.
186    # In order to support them, we create a feature with the env that the action
187    # config will enable, and imply it in the action config.
188    feature = None
189    if untyped_flags.env_sets:
190        feature = rules_cc_feature(
191            name = "implied_by_%s" % action_config.action_name,
192            env_sets = untyped_flags.env_sets,
193        )
194        implies.append(feature.name)
195
196    return struct(
197        action_config = rules_cc_action_config(
198            action_name = action_config.action_name,
199            enabled = action_config.enabled,
200            tools = [
201                _to_untyped_tool(tool, known, fail = fail)
202                for tool in action_config.tools
203            ],
204            flag_sets = [
205                # Make the flag sets actionless.
206                rules_cc_flag_set(
207                    actions = [],
208                    with_features = fs.with_features,
209                    flag_groups = fs.flag_groups,
210                )
211                for fs in untyped_flags.flag_sets
212            ],
213            implies = implies,
214        ),
215        features = [feature] if feature else [],
216    )
217
218def to_untyped_config(feature_set, action_config_set, flag_sets, extra_action_files, fail = fail):
219    """Converts Pigweed providers into a format suitable for rules_cc.
220
221    Args:
222        feature_set: PwFeatureSetInfo: Features available in the toolchain
223        action_config_set: ActionConfigSetInfo: Set of defined action configs
224        flag_sets: Flag sets that are unconditionally applied
225        extra_action_files: Files to be added to actions
226        fail: The fail function. Only change this during testing.
227    Returns:
228        A struct containing parameters suitable to pass to
229          cc_common.create_cc_toolchain_config_info.
230    """
231    flag_sets_by_action = {}
232    for flag_set in flag_sets:
233        for action in flag_set.actions:
234            flag_sets_by_action.setdefault(action, []).append(flag_set)
235
236    known_labels = {}
237    known_feature_names = {}
238    feature_list = feature_set.features.to_list()
239    for feature in feature_list:
240        known_labels[feature.label] = None
241        existing_feature = known_feature_names.get(feature.name, None)
242        if existing_feature != None and feature.overrides != existing_feature and existing_feature.overrides != feature:
243            fail("Conflicting features: %s and %s both have feature name %s" % (feature.label, existing_feature.label, feature.name))
244
245        known_feature_names[feature.name] = feature
246
247    untyped_features = []
248    for feature in feature_list:
249        untyped_feature = _to_untyped_feature(feature, known = known_labels, fail = fail)
250        if untyped_feature != None:
251            untyped_features.append(untyped_feature)
252
253    acs = action_config_set.action_configs.to_list()
254    known_actions = {}
255    untyped_acs = []
256    for ac in acs:
257        if ac.action_name in known_actions:
258            fail("In %s, both %s and %s implement %s" % (
259                action_config_set.label,
260                ac.label,
261                known_actions[ac.action_name],
262                ac.action_name,
263            ))
264        known_actions[ac.action_name] = ac.label
265        out_ac = _to_untyped_action_config(
266            ac,
267            extra_flag_sets = flag_sets_by_action.get(ac.action_name, []),
268            known = known_labels,
269            fail = fail,
270        )
271        untyped_acs.append(out_ac.action_config)
272        untyped_features.extend(out_ac.features)
273
274    action_to_files = {
275        ac.action_name: [ac.files]
276        for ac in acs
277    }
278    for ffa in extra_action_files.srcs.to_list():
279        action_to_files.setdefault(ffa.action, []).append(ffa.files)
280
281    return struct(
282        features = untyped_features,
283        action_configs = untyped_acs,
284        action_to_files = {k: depset(transitive = v) for k, v in action_to_files.items()},
285    )
286