xref: /aosp_15_r20/external/bazel-skylib/lib/selects.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1*bcb5dc79SHONG Yifan# Copyright 2017 The Bazel Authors. All rights reserved.
2*bcb5dc79SHONG Yifan#
3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License");
4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License.
5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at
6*bcb5dc79SHONG Yifan#
7*bcb5dc79SHONG Yifan#    http://www.apache.org/licenses/LICENSE-2.0
8*bcb5dc79SHONG Yifan#
9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software
10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS,
11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and
13*bcb5dc79SHONG Yifan# limitations under the License.
14*bcb5dc79SHONG Yifan
15*bcb5dc79SHONG Yifan"""Skylib module containing convenience interfaces for select()."""
16*bcb5dc79SHONG Yifan
17*bcb5dc79SHONG Yifandef _with_or(input_dict, no_match_error = ""):
18*bcb5dc79SHONG Yifan    """Drop-in replacement for `select()` that supports ORed keys.
19*bcb5dc79SHONG Yifan
20*bcb5dc79SHONG Yifan    Example:
21*bcb5dc79SHONG Yifan
22*bcb5dc79SHONG Yifan          ```build
23*bcb5dc79SHONG Yifan          deps = selects.with_or({
24*bcb5dc79SHONG Yifan              "//configs:one": [":dep1"],
25*bcb5dc79SHONG Yifan              ("//configs:two", "//configs:three"): [":dep2or3"],
26*bcb5dc79SHONG Yifan              "//configs:four": [":dep4"],
27*bcb5dc79SHONG Yifan              "//conditions:default": [":default"]
28*bcb5dc79SHONG Yifan          })
29*bcb5dc79SHONG Yifan          ```
30*bcb5dc79SHONG Yifan
31*bcb5dc79SHONG Yifan          Key labels may appear at most once anywhere in the input.
32*bcb5dc79SHONG Yifan
33*bcb5dc79SHONG Yifan    Args:
34*bcb5dc79SHONG Yifan      input_dict: The same dictionary `select()` takes, except keys may take
35*bcb5dc79SHONG Yifan          either the usual form `"//foo:config1"` or
36*bcb5dc79SHONG Yifan          `("//foo:config1", "//foo:config2", ...)` to signify
37*bcb5dc79SHONG Yifan          `//foo:config1` OR `//foo:config2` OR `...`.
38*bcb5dc79SHONG Yifan      no_match_error: Optional custom error to report if no condition matches.
39*bcb5dc79SHONG Yifan
40*bcb5dc79SHONG Yifan    Returns:
41*bcb5dc79SHONG Yifan      A native `select()` that expands
42*bcb5dc79SHONG Yifan
43*bcb5dc79SHONG Yifan      `("//configs:two", "//configs:three"): [":dep2or3"]`
44*bcb5dc79SHONG Yifan
45*bcb5dc79SHONG Yifan      to
46*bcb5dc79SHONG Yifan
47*bcb5dc79SHONG Yifan      ```build
48*bcb5dc79SHONG Yifan      "//configs:two": [":dep2or3"],
49*bcb5dc79SHONG Yifan      "//configs:three": [":dep2or3"],
50*bcb5dc79SHONG Yifan      ```
51*bcb5dc79SHONG Yifan    """
52*bcb5dc79SHONG Yifan    return select(_with_or_dict(input_dict), no_match_error = no_match_error)
53*bcb5dc79SHONG Yifan
54*bcb5dc79SHONG Yifandef _with_or_dict(input_dict):
55*bcb5dc79SHONG Yifan    """Variation of `with_or` that returns the dict of the `select()`.
56*bcb5dc79SHONG Yifan
57*bcb5dc79SHONG Yifan    Unlike `select()`, the contents of the dict can be inspected by Starlark
58*bcb5dc79SHONG Yifan    macros.
59*bcb5dc79SHONG Yifan
60*bcb5dc79SHONG Yifan    Args:
61*bcb5dc79SHONG Yifan      input_dict: Same as `with_or`.
62*bcb5dc79SHONG Yifan
63*bcb5dc79SHONG Yifan    Returns:
64*bcb5dc79SHONG Yifan      A dictionary usable by a native `select()`.
65*bcb5dc79SHONG Yifan    """
66*bcb5dc79SHONG Yifan    output_dict = {}
67*bcb5dc79SHONG Yifan    for (key, value) in input_dict.items():
68*bcb5dc79SHONG Yifan        if type(key) == type(()):
69*bcb5dc79SHONG Yifan            for config_setting in key:
70*bcb5dc79SHONG Yifan                if config_setting in output_dict.keys():
71*bcb5dc79SHONG Yifan                    fail("key %s appears multiple times" % config_setting)
72*bcb5dc79SHONG Yifan                output_dict[config_setting] = value
73*bcb5dc79SHONG Yifan        else:
74*bcb5dc79SHONG Yifan            if key in output_dict.keys():
75*bcb5dc79SHONG Yifan                fail("key %s appears multiple times" % key)
76*bcb5dc79SHONG Yifan            output_dict[key] = value
77*bcb5dc79SHONG Yifan    return output_dict
78*bcb5dc79SHONG Yifan
79*bcb5dc79SHONG Yifandef _config_setting_group(name, match_any = [], match_all = [], visibility = None):
80*bcb5dc79SHONG Yifan    """Matches if all or any of its member `config_setting`s match.
81*bcb5dc79SHONG Yifan
82*bcb5dc79SHONG Yifan    Example:
83*bcb5dc79SHONG Yifan
84*bcb5dc79SHONG Yifan      ```build
85*bcb5dc79SHONG Yifan      config_setting(name = "one", define_values = {"foo": "true"})
86*bcb5dc79SHONG Yifan      config_setting(name = "two", define_values = {"bar": "false"})
87*bcb5dc79SHONG Yifan      config_setting(name = "three", define_values = {"baz": "more_false"})
88*bcb5dc79SHONG Yifan
89*bcb5dc79SHONG Yifan      config_setting_group(
90*bcb5dc79SHONG Yifan          name = "one_two_three",
91*bcb5dc79SHONG Yifan          match_all = [":one", ":two", ":three"]
92*bcb5dc79SHONG Yifan      )
93*bcb5dc79SHONG Yifan
94*bcb5dc79SHONG Yifan      cc_binary(
95*bcb5dc79SHONG Yifan          name = "myapp",
96*bcb5dc79SHONG Yifan          srcs = ["myapp.cc"],
97*bcb5dc79SHONG Yifan          deps = select({
98*bcb5dc79SHONG Yifan              ":one_two_three": [":special_deps"],
99*bcb5dc79SHONG Yifan              "//conditions:default": [":default_deps"]
100*bcb5dc79SHONG Yifan          })
101*bcb5dc79SHONG Yifan      ```
102*bcb5dc79SHONG Yifan
103*bcb5dc79SHONG Yifan    Args:
104*bcb5dc79SHONG Yifan      name: The group's name. This is how `select()`s reference it.
105*bcb5dc79SHONG Yifan      match_any: A list of `config_settings`. This group matches if *any* member
106*bcb5dc79SHONG Yifan          in the list matches. If this is set, `match_all` must not be set.
107*bcb5dc79SHONG Yifan      match_all: A list of `config_settings`. This group matches if *every*
108*bcb5dc79SHONG Yifan          member in the list matches. If this is set, `match_any` must be not
109*bcb5dc79SHONG Yifan          set.
110*bcb5dc79SHONG Yifan      visibility: Visibility of the config_setting_group.
111*bcb5dc79SHONG Yifan    """
112*bcb5dc79SHONG Yifan    empty1 = not bool(len(match_any))
113*bcb5dc79SHONG Yifan    empty2 = not bool(len(match_all))
114*bcb5dc79SHONG Yifan    if (empty1 and empty2) or (not empty1 and not empty2):
115*bcb5dc79SHONG Yifan        fail('Either "match_any" or "match_all" must be set, but not both.')
116*bcb5dc79SHONG Yifan    _check_duplicates(match_any)
117*bcb5dc79SHONG Yifan    _check_duplicates(match_all)
118*bcb5dc79SHONG Yifan
119*bcb5dc79SHONG Yifan    if ((len(match_any) == 1 and match_any[0] == "//conditions:default") or
120*bcb5dc79SHONG Yifan        (len(match_all) == 1 and match_all[0] == "//conditions:default")):
121*bcb5dc79SHONG Yifan        # If the only entry is "//conditions:default", the condition is
122*bcb5dc79SHONG Yifan        # automatically true.
123*bcb5dc79SHONG Yifan        _config_setting_always_true(name, visibility)
124*bcb5dc79SHONG Yifan    elif not empty1:
125*bcb5dc79SHONG Yifan        _config_setting_or_group(name, match_any, visibility)
126*bcb5dc79SHONG Yifan    else:
127*bcb5dc79SHONG Yifan        _config_setting_and_group(name, match_all, visibility)
128*bcb5dc79SHONG Yifan
129*bcb5dc79SHONG Yifandef _check_duplicates(settings):
130*bcb5dc79SHONG Yifan    """Fails if any entry in settings appears more than once."""
131*bcb5dc79SHONG Yifan    seen = {}
132*bcb5dc79SHONG Yifan    for setting in settings:
133*bcb5dc79SHONG Yifan        if setting in seen:
134*bcb5dc79SHONG Yifan            fail(setting + " appears more than once. Duplicates not allowed.")
135*bcb5dc79SHONG Yifan        seen[setting] = True
136*bcb5dc79SHONG Yifan
137*bcb5dc79SHONG Yifandef _remove_default_condition(settings):
138*bcb5dc79SHONG Yifan    """Returns settings with "//conditions:default" entries filtered out."""
139*bcb5dc79SHONG Yifan    new_settings = []
140*bcb5dc79SHONG Yifan    for setting in settings:
141*bcb5dc79SHONG Yifan        if settings != "//conditions:default":
142*bcb5dc79SHONG Yifan            new_settings.append(setting)
143*bcb5dc79SHONG Yifan    return new_settings
144*bcb5dc79SHONG Yifan
145*bcb5dc79SHONG Yifandef _config_setting_or_group(name, settings, visibility):
146*bcb5dc79SHONG Yifan    """ORs multiple config_settings together (inclusively).
147*bcb5dc79SHONG Yifan
148*bcb5dc79SHONG Yifan    The core idea is to create a sequential chain of alias targets where each is
149*bcb5dc79SHONG Yifan    select-resolved as follows: If alias n matches config_setting n, the chain
150*bcb5dc79SHONG Yifan    is true so it resolves to config_setting n. Else it resolves to alias n+1
151*bcb5dc79SHONG Yifan    (which checks config_setting n+1, and so on). If none of the config_settings
152*bcb5dc79SHONG Yifan    match, the final alias resolves to one of them arbitrarily, which by
153*bcb5dc79SHONG Yifan    definition doesn't match.
154*bcb5dc79SHONG Yifan    """
155*bcb5dc79SHONG Yifan
156*bcb5dc79SHONG Yifan    # "//conditions:default" is present, the whole chain is automatically true.
157*bcb5dc79SHONG Yifan    if len(_remove_default_condition(settings)) < len(settings):
158*bcb5dc79SHONG Yifan        _config_setting_always_true(name, visibility)
159*bcb5dc79SHONG Yifan        return
160*bcb5dc79SHONG Yifan
161*bcb5dc79SHONG Yifan    elif len(settings) == 1:  # One entry? Just alias directly to it.
162*bcb5dc79SHONG Yifan        native.alias(
163*bcb5dc79SHONG Yifan            name = name,
164*bcb5dc79SHONG Yifan            actual = settings[0],
165*bcb5dc79SHONG Yifan            visibility = visibility,
166*bcb5dc79SHONG Yifan        )
167*bcb5dc79SHONG Yifan        return
168*bcb5dc79SHONG Yifan
169*bcb5dc79SHONG Yifan    # We need n-1 aliases for n settings. The first alias has no extension. The
170*bcb5dc79SHONG Yifan    # second alias is named name + "_2", and so on. For the first n-2 aliases,
171*bcb5dc79SHONG Yifan    # if they don't match they reference the next alias over. If the n-1st alias
172*bcb5dc79SHONG Yifan    # doesn't match, it references the final setting (which is then evaluated
173*bcb5dc79SHONG Yifan    # directly to determine the final value of the AND chain).
174*bcb5dc79SHONG Yifan    actual = [name + "_" + str(i) for i in range(2, len(settings))]
175*bcb5dc79SHONG Yifan    actual.append(settings[-1])
176*bcb5dc79SHONG Yifan
177*bcb5dc79SHONG Yifan    for i in range(1, len(settings)):
178*bcb5dc79SHONG Yifan        native.alias(
179*bcb5dc79SHONG Yifan            name = name if i == 1 else name + "_" + str(i),
180*bcb5dc79SHONG Yifan            actual = select({
181*bcb5dc79SHONG Yifan                settings[i - 1]: settings[i - 1],
182*bcb5dc79SHONG Yifan                "//conditions:default": actual[i - 1],
183*bcb5dc79SHONG Yifan            }),
184*bcb5dc79SHONG Yifan            visibility = visibility if i == 1 else ["//visibility:private"],
185*bcb5dc79SHONG Yifan        )
186*bcb5dc79SHONG Yifan
187*bcb5dc79SHONG Yifandef _config_setting_and_group(name, settings, visibility):
188*bcb5dc79SHONG Yifan    """ANDs multiple config_settings together.
189*bcb5dc79SHONG Yifan
190*bcb5dc79SHONG Yifan    The core idea is to create a sequential chain of alias targets where each is
191*bcb5dc79SHONG Yifan    select-resolved as follows: If alias n matches config_setting n, it resolves to
192*bcb5dc79SHONG Yifan    alias n+1 (which evaluates config_setting n+1, and so on). Else it resolves to
193*bcb5dc79SHONG Yifan    config_setting n, which doesn't match by definition. The only way to get a
194*bcb5dc79SHONG Yifan    matching final result is if all config_settings match.
195*bcb5dc79SHONG Yifan    """
196*bcb5dc79SHONG Yifan
197*bcb5dc79SHONG Yifan    # "//conditions:default" is automatically true so doesn't need checking.
198*bcb5dc79SHONG Yifan    settings = _remove_default_condition(settings)
199*bcb5dc79SHONG Yifan
200*bcb5dc79SHONG Yifan    # One config_setting input? Just alias directly to it.
201*bcb5dc79SHONG Yifan    if len(settings) == 1:
202*bcb5dc79SHONG Yifan        native.alias(
203*bcb5dc79SHONG Yifan            name = name,
204*bcb5dc79SHONG Yifan            actual = settings[0],
205*bcb5dc79SHONG Yifan            visibility = visibility,
206*bcb5dc79SHONG Yifan        )
207*bcb5dc79SHONG Yifan        return
208*bcb5dc79SHONG Yifan
209*bcb5dc79SHONG Yifan    # We need n-1 aliases for n settings. The first alias has no extension. The
210*bcb5dc79SHONG Yifan    # second alias is named name + "_2", and so on. For the first n-2 aliases,
211*bcb5dc79SHONG Yifan    # if they match they reference the next alias over. If the n-1st alias matches,
212*bcb5dc79SHONG Yifan    # it references the final setting (which is then evaluated directly to determine
213*bcb5dc79SHONG Yifan    # the final value of the AND chain).
214*bcb5dc79SHONG Yifan    actual = [name + "_" + str(i) for i in range(2, len(settings))]
215*bcb5dc79SHONG Yifan    actual.append(settings[-1])
216*bcb5dc79SHONG Yifan
217*bcb5dc79SHONG Yifan    for i in range(1, len(settings)):
218*bcb5dc79SHONG Yifan        native.alias(
219*bcb5dc79SHONG Yifan            name = name if i == 1 else name + "_" + str(i),
220*bcb5dc79SHONG Yifan            actual = select({
221*bcb5dc79SHONG Yifan                settings[i - 1]: actual[i - 1],
222*bcb5dc79SHONG Yifan                "//conditions:default": settings[i - 1],
223*bcb5dc79SHONG Yifan            }),
224*bcb5dc79SHONG Yifan            visibility = visibility if i == 1 else ["//visibility:private"],
225*bcb5dc79SHONG Yifan        )
226*bcb5dc79SHONG Yifan
227*bcb5dc79SHONG Yifandef _config_setting_always_true(name, visibility):
228*bcb5dc79SHONG Yifan    """Returns a config_setting with the given name that's always true.
229*bcb5dc79SHONG Yifan
230*bcb5dc79SHONG Yifan    This is achieved by constructing a two-entry OR chain where each
231*bcb5dc79SHONG Yifan    config_setting takes opposite values of a boolean flag.
232*bcb5dc79SHONG Yifan    """
233*bcb5dc79SHONG Yifan    name_on = name + "_stamp_binary_on_check"
234*bcb5dc79SHONG Yifan    name_off = name + "_stamp_binary_off_check"
235*bcb5dc79SHONG Yifan    native.config_setting(
236*bcb5dc79SHONG Yifan        name = name_on,
237*bcb5dc79SHONG Yifan        values = {"stamp": "1"},
238*bcb5dc79SHONG Yifan    )
239*bcb5dc79SHONG Yifan    native.config_setting(
240*bcb5dc79SHONG Yifan        name = name_off,
241*bcb5dc79SHONG Yifan        values = {"stamp": "0"},
242*bcb5dc79SHONG Yifan    )
243*bcb5dc79SHONG Yifan    return _config_setting_or_group(name, [":" + name_on, ":" + name_off], visibility)
244*bcb5dc79SHONG Yifan
245*bcb5dc79SHONG Yifanselects = struct(
246*bcb5dc79SHONG Yifan    with_or = _with_or,
247*bcb5dc79SHONG Yifan    with_or_dict = _with_or_dict,
248*bcb5dc79SHONG Yifan    config_setting_group = _config_setting_group,
249*bcb5dc79SHONG Yifan)
250