flags.py (8446B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import re 6 from collections import OrderedDict 7 8 from packaging.version import Version 9 10 from mozpack.errors import errors 11 12 13 class Flag: 14 """ 15 Class for flags in manifest entries in the form: 16 "flag" (same as "flag=true") 17 "flag=yes|true|1" 18 "flag=no|false|0" 19 """ 20 21 def __init__(self, name): 22 """ 23 Initialize a Flag with the given name. 24 """ 25 self.name = name 26 self.value = None 27 28 def add_definition(self, definition): 29 """ 30 Add a flag value definition. Replaces any previously set value. 31 """ 32 if definition == self.name: 33 self.value = True 34 return 35 assert definition.startswith(self.name) 36 if definition[len(self.name)] != "=": 37 return errors.fatal("Malformed flag: %s" % definition) 38 value = definition[len(self.name) + 1 :] 39 if value in ("yes", "true", "1", "no", "false", "0"): 40 self.value = value 41 else: 42 return errors.fatal("Unknown value in: %s" % definition) 43 44 def matches(self, value): 45 """ 46 Return whether the flag value matches the given value. The values 47 are canonicalized for comparison. 48 """ 49 if value in ("yes", "true", "1", True): 50 return self.value in ("yes", "true", "1", True) 51 if value in ("no", "false", "0", False): 52 return self.value in ("no", "false", "0", False, None) 53 raise RuntimeError("Invalid value: %s" % value) 54 55 def __str__(self): 56 """ 57 Serialize the flag value in the same form given to the last 58 add_definition() call. 59 """ 60 if self.value is None: 61 return "" 62 if self.value is True: 63 return self.name 64 return "%s=%s" % (self.name, self.value) 65 66 def __eq__(self, other): 67 return str(self) == other 68 69 70 class StringFlag: 71 """ 72 Class for string flags in manifest entries in the form: 73 "flag=string" 74 "flag!=string" 75 """ 76 77 def __init__(self, name): 78 """ 79 Initialize a StringFlag with the given name. 80 """ 81 self.name = name 82 self.values = [] 83 84 def add_definition(self, definition): 85 """ 86 Add a string flag definition. 87 """ 88 assert definition.startswith(self.name) 89 value = definition[len(self.name) :] 90 if value.startswith("="): 91 self.values.append(("==", value[1:])) 92 elif value.startswith("!="): 93 self.values.append(("!=", value[2:])) 94 else: 95 return errors.fatal("Malformed flag: %s" % definition) 96 97 def matches(self, value): 98 """ 99 Return whether one of the string flag definitions matches the given 100 value. 101 For example, 102 103 flag = StringFlag('foo') 104 flag.add_definition('foo!=bar') 105 flag.matches('bar') returns False 106 flag.matches('qux') returns True 107 flag = StringFlag('foo') 108 flag.add_definition('foo=bar') 109 flag.add_definition('foo=baz') 110 flag.matches('bar') returns True 111 flag.matches('baz') returns True 112 flag.matches('qux') returns False 113 """ 114 if not self.values: 115 return True 116 for comparison, val in self.values: 117 if eval("value %s val" % comparison): 118 return True 119 return False 120 121 def __str__(self): 122 """ 123 Serialize the flag definitions in the same form given to each 124 add_definition() call. 125 """ 126 res = [] 127 for comparison, val in self.values: 128 if comparison == "==": 129 res.append("%s=%s" % (self.name, val)) 130 else: 131 res.append("%s!=%s" % (self.name, val)) 132 return " ".join(res) 133 134 def __eq__(self, other): 135 return str(self) == other 136 137 138 class VersionFlag: 139 """ 140 Class for version flags in manifest entries in the form: 141 "flag=version" 142 "flag<=version" 143 "flag<version" 144 "flag>=version" 145 "flag>version" 146 """ 147 148 def __init__(self, name): 149 """ 150 Initialize a VersionFlag with the given name. 151 """ 152 self.name = name 153 self.values = [] 154 155 def add_definition(self, definition): 156 """ 157 Add a version flag definition. 158 """ 159 assert definition.startswith(self.name) 160 value = definition[len(self.name) :] 161 if value.startswith("="): 162 self.values.append(("==", Version(value[1:]))) 163 elif len(value) > 1 and value[0] in ["<", ">"]: 164 if value[1] == "=": 165 if len(value) < 3: 166 return errors.fatal("Malformed flag: %s" % definition) 167 self.values.append((value[0:2], Version(value[2:]))) 168 else: 169 self.values.append((value[0], Version(value[1:]))) 170 else: 171 return errors.fatal("Malformed flag: %s" % definition) 172 173 def matches(self, value): 174 """ 175 Return whether one of the version flag definitions matches the given 176 value. 177 For example, 178 179 flag = VersionFlag('foo') 180 flag.add_definition('foo>=1.0') 181 flag.matches('1.0') returns True 182 flag.matches('1.1') returns True 183 flag.matches('0.9') returns False 184 flag = VersionFlag('foo') 185 flag.add_definition('foo>=1.0') 186 flag.add_definition('foo<0.5') 187 flag.matches('0.4') returns True 188 flag.matches('1.0') returns True 189 flag.matches('0.6') returns False 190 """ 191 value = Version(value) 192 if not self.values: 193 return True 194 for comparison, val in self.values: 195 if eval("value %s val" % comparison): 196 return True 197 return False 198 199 def __str__(self): 200 """ 201 Serialize the flag definitions in the same form given to each 202 add_definition() call. 203 """ 204 res = [] 205 for comparison, val in self.values: 206 if comparison == "==": 207 res.append("%s=%s" % (self.name, val)) 208 else: 209 res.append("%s%s%s" % (self.name, comparison, val)) 210 return " ".join(res) 211 212 def __eq__(self, other): 213 return str(self) == other 214 215 216 class Flags(OrderedDict): 217 """ 218 Class to handle a set of flags definitions given on a single manifest 219 entry. 220 221 """ 222 223 FLAGS = { 224 "application": StringFlag, 225 "appversion": VersionFlag, 226 "platformversion": VersionFlag, 227 "contentaccessible": Flag, 228 "os": StringFlag, 229 "osversion": VersionFlag, 230 "abi": StringFlag, 231 "platform": Flag, 232 "xpcnativewrappers": Flag, 233 "tablet": Flag, 234 "process": StringFlag, 235 "backgroundtask": StringFlag, 236 } 237 RE = re.compile(r"([!<>=]+)") 238 239 def __init__(self, *flags): 240 """ 241 Initialize a set of flags given in string form. 242 flags = Flags('contentaccessible=yes', 'appversion>=3.5') 243 """ 244 OrderedDict.__init__(self) 245 for f in flags: 246 name = self.RE.split(f) 247 name = name[0] 248 if name not in self.FLAGS: 249 errors.fatal("Unknown flag: %s" % name) 250 continue 251 if name not in self: 252 self[name] = self.FLAGS[name](name) 253 self[name].add_definition(f) 254 255 def __str__(self): 256 """ 257 Serialize the set of flags. 258 """ 259 return " ".join(str(self[k]) for k in self) 260 261 def match(self, **filter): 262 """ 263 Return whether the set of flags match the set of given filters. 264 flags = Flags('contentaccessible=yes', 'appversion>=3.5', 265 'application=foo') 266 267 flags.match(application='foo') returns True 268 flags.match(application='foo', appversion='3.5') returns True 269 flags.match(application='foo', appversion='3.0') returns False 270 271 """ 272 for name, value in filter.items(): 273 if name not in self: 274 continue 275 if not self[name].matches(value): 276 return False 277 return True