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