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
|
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Skylib module containing convenience interfaces for select()."""
def _with_or(input_dict, no_match_error = ""):
"""Drop-in replacement for `select()` that supports ORed keys.
Example:
```build
deps = selects.with_or({
"//configs:one": [":dep1"],
("//configs:two", "//configs:three"): [":dep2or3"],
"//configs:four": [":dep4"],
"//conditions:default": [":default"]
})
```
Key labels may appear at most once anywhere in the input.
Args:
input_dict: The same dictionary `select()` takes, except keys may take
either the usual form `"//foo:config1"` or
`("//foo:config1", "//foo:config2", ...)` to signify
`//foo:config1` OR `//foo:config2` OR `...`.
no_match_error: Optional custom error to report if no condition matches.
Returns:
A native `select()` that expands
`("//configs:two", "//configs:three"): [":dep2or3"]`
to
```build
"//configs:two": [":dep2or3"],
"//configs:three": [":dep2or3"],
```
"""
return select(_with_or_dict(input_dict), no_match_error = no_match_error)
def _with_or_dict(input_dict):
"""Variation of `with_or` that returns the dict of the `select()`.
Unlike `select()`, the contents of the dict can be inspected by Starlark
macros.
Args:
input_dict: Same as `with_or`.
Returns:
A dictionary usable by a native `select()`.
"""
output_dict = {}
for (key, value) in input_dict.items():
if type(key) == type(()):
for config_setting in key:
if config_setting in output_dict.keys():
fail("key %s appears multiple times" % config_setting)
output_dict[config_setting] = value
else:
if key in output_dict.keys():
fail("key %s appears multiple times" % key)
output_dict[key] = value
return output_dict
def _config_setting_group(name, match_any = [], match_all = [], visibility = None):
"""Matches if all or any of its member `config_setting`s match.
Example:
```build
config_setting(name = "one", define_values = {"foo": "true"})
config_setting(name = "two", define_values = {"bar": "false"})
config_setting(name = "three", define_values = {"baz": "more_false"})
config_setting_group(
name = "one_two_three",
match_all = [":one", ":two", ":three"]
)
cc_binary(
name = "myapp",
srcs = ["myapp.cc"],
deps = select({
":one_two_three": [":special_deps"],
"//conditions:default": [":default_deps"]
})
```
Args:
name: The group's name. This is how `select()`s reference it.
match_any: A list of `config_settings`. This group matches if *any* member
in the list matches. If this is set, `match_all` must not be set.
match_all: A list of `config_settings`. This group matches if *every*
member in the list matches. If this is set, `match_any` must be not
set.
visibility: Visibility of the config_setting_group.
"""
empty1 = not bool(len(match_any))
empty2 = not bool(len(match_all))
if (empty1 and empty2) or (not empty1 and not empty2):
fail('Either "match_any" or "match_all" must be set, but not both.')
_check_duplicates(match_any)
_check_duplicates(match_all)
if ((len(match_any) == 1 and match_any[0] == "//conditions:default") or
(len(match_all) == 1 and match_all[0] == "//conditions:default")):
# If the only entry is "//conditions:default", the condition is
# automatically true.
_config_setting_always_true(name, visibility)
elif not empty1:
_config_setting_or_group(name, match_any, visibility)
else:
_config_setting_and_group(name, match_all, visibility)
def _check_duplicates(settings):
"""Fails if any entry in settings appears more than once."""
seen = {}
for setting in settings:
if setting in seen:
fail(setting + " appears more than once. Duplicates not allowed.")
seen[setting] = True
def _remove_default_condition(settings):
"""Returns settings with "//conditions:default" entries filtered out."""
new_settings = []
for setting in settings:
if settings != "//conditions:default":
new_settings.append(setting)
return new_settings
def _config_setting_or_group(name, settings, visibility):
"""ORs multiple config_settings together (inclusively).
The core idea is to create a sequential chain of alias targets where each is
select-resolved as follows: If alias n matches config_setting n, the chain
is true so it resolves to config_setting n. Else it resolves to alias n+1
(which checks config_setting n+1, and so on). If none of the config_settings
match, the final alias resolves to one of them arbitrarily, which by
definition doesn't match.
"""
# "//conditions:default" is present, the whole chain is automatically true.
if len(_remove_default_condition(settings)) < len(settings):
_config_setting_always_true(name, visibility)
return
elif len(settings) == 1: # One entry? Just alias directly to it.
native.alias(
name = name,
actual = settings[0],
visibility = visibility,
)
return
# We need n-1 aliases for n settings. The first alias has no extension. The
# second alias is named name + "_2", and so on. For the first n-2 aliases,
# if they don't match they reference the next alias over. If the n-1st alias
# doesn't match, it references the final setting (which is then evaluated
# directly to determine the final value of the AND chain).
actual = [name + "_" + str(i) for i in range(2, len(settings))]
actual.append(settings[-1])
for i in range(1, len(settings)):
native.alias(
name = name if i == 1 else name + "_" + str(i),
actual = select({
settings[i - 1]: settings[i - 1],
"//conditions:default": actual[i - 1],
}),
visibility = visibility if i == 1 else ["//visibility:private"],
)
def _config_setting_and_group(name, settings, visibility):
"""ANDs multiple config_settings together.
The core idea is to create a sequential chain of alias targets where each is
select-resolved as follows: If alias n matches config_setting n, it resolves to
alias n+1 (which evaluates config_setting n+1, and so on). Else it resolves to
config_setting n, which doesn't match by definition. The only way to get a
matching final result is if all config_settings match.
"""
# "//conditions:default" is automatically true so doesn't need checking.
settings = _remove_default_condition(settings)
# One config_setting input? Just alias directly to it.
if len(settings) == 1:
native.alias(
name = name,
actual = settings[0],
visibility = visibility,
)
return
# We need n-1 aliases for n settings. The first alias has no extension. The
# second alias is named name + "_2", and so on. For the first n-2 aliases,
# if they match they reference the next alias over. If the n-1st alias matches,
# it references the final setting (which is then evaluated directly to determine
# the final value of the AND chain).
actual = [name + "_" + str(i) for i in range(2, len(settings))]
actual.append(settings[-1])
for i in range(1, len(settings)):
native.alias(
name = name if i == 1 else name + "_" + str(i),
actual = select({
settings[i - 1]: actual[i - 1],
"//conditions:default": settings[i - 1],
}),
visibility = visibility if i == 1 else ["//visibility:private"],
)
def _config_setting_always_true(name, visibility):
"""Returns a config_setting with the given name that's always true.
This is achieved by constructing a two-entry OR chain where each
config_setting takes opposite values of a boolean flag.
"""
name_on = name + "_stamp_binary_on_check"
name_off = name + "_stamp_binary_off_check"
native.config_setting(
name = name_on,
values = {"stamp": "1"},
)
native.config_setting(
name = name_off,
values = {"stamp": "0"},
)
return _config_setting_or_group(name, [":" + name_on, ":" + name_off], visibility)
selects = struct(
with_or = _with_or,
with_or_dict = _with_or_dict,
config_setting_group = _config_setting_group,
)
|