1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/python/mozbuild/mozpack/chrome/flags.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,255 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +import re 1.9 +from distutils.version import LooseVersion 1.10 +from mozpack.errors import errors 1.11 +from collections import OrderedDict 1.12 + 1.13 + 1.14 +class Flag(object): 1.15 + ''' 1.16 + Class for flags in manifest entries in the form: 1.17 + "flag" (same as "flag=true") 1.18 + "flag=yes|true|1" 1.19 + "flag=no|false|0" 1.20 + ''' 1.21 + def __init__(self, name): 1.22 + ''' 1.23 + Initialize a Flag with the given name. 1.24 + ''' 1.25 + self.name = name 1.26 + self.value = None 1.27 + 1.28 + def add_definition(self, definition): 1.29 + ''' 1.30 + Add a flag value definition. Replaces any previously set value. 1.31 + ''' 1.32 + if definition == self.name: 1.33 + self.value = True 1.34 + return 1.35 + assert(definition.startswith(self.name)) 1.36 + if definition[len(self.name)] != '=': 1.37 + return errors.fatal('Malformed flag: %s' % definition) 1.38 + value = definition[len(self.name) + 1:] 1.39 + if value in ('yes', 'true', '1', 'no', 'false', '0'): 1.40 + self.value = value 1.41 + else: 1.42 + return errors.fatal('Unknown value in: %s' % definition) 1.43 + 1.44 + def matches(self, value): 1.45 + ''' 1.46 + Return whether the flag value matches the given value. The values 1.47 + are canonicalized for comparison. 1.48 + ''' 1.49 + if value in ('yes', 'true', '1', True): 1.50 + return self.value in ('yes', 'true', '1', True) 1.51 + if value in ('no', 'false', '0', False): 1.52 + return self.value in ('no', 'false', '0', False, None) 1.53 + raise RuntimeError('Invalid value: %s' % value) 1.54 + 1.55 + def __str__(self): 1.56 + ''' 1.57 + Serialize the flag value in the same form given to the last 1.58 + add_definition() call. 1.59 + ''' 1.60 + if self.value is None: 1.61 + return '' 1.62 + if self.value is True: 1.63 + return self.name 1.64 + return '%s=%s' % (self.name, self.value) 1.65 + 1.66 + 1.67 +class StringFlag(object): 1.68 + ''' 1.69 + Class for string flags in manifest entries in the form: 1.70 + "flag=string" 1.71 + "flag!=string" 1.72 + ''' 1.73 + def __init__(self, name): 1.74 + ''' 1.75 + Initialize a StringFlag with the given name. 1.76 + ''' 1.77 + self.name = name 1.78 + self.values = [] 1.79 + 1.80 + def add_definition(self, definition): 1.81 + ''' 1.82 + Add a string flag definition. 1.83 + ''' 1.84 + assert(definition.startswith(self.name)) 1.85 + value = definition[len(self.name):] 1.86 + if value.startswith('='): 1.87 + self.values.append(('==', value[1:])) 1.88 + elif value.startswith('!='): 1.89 + self.values.append(('!=', value[2:])) 1.90 + else: 1.91 + return errors.fatal('Malformed flag: %s' % definition) 1.92 + 1.93 + def matches(self, value): 1.94 + ''' 1.95 + Return whether one of the string flag definitions matches the given 1.96 + value. 1.97 + For example, 1.98 + flag = StringFlag('foo') 1.99 + flag.add_definition('foo!=bar') 1.100 + flag.matches('bar') returns False 1.101 + flag.matches('qux') returns True 1.102 + flag = StringFlag('foo') 1.103 + flag.add_definition('foo=bar') 1.104 + flag.add_definition('foo=baz') 1.105 + flag.matches('bar') returns True 1.106 + flag.matches('baz') returns True 1.107 + flag.matches('qux') returns False 1.108 + ''' 1.109 + if not self.values: 1.110 + return True 1.111 + for comparison, val in self.values: 1.112 + if eval('value %s val' % comparison): 1.113 + return True 1.114 + return False 1.115 + 1.116 + def __str__(self): 1.117 + ''' 1.118 + Serialize the flag definitions in the same form given to each 1.119 + add_definition() call. 1.120 + ''' 1.121 + res = [] 1.122 + for comparison, val in self.values: 1.123 + if comparison == '==': 1.124 + res.append('%s=%s' % (self.name, val)) 1.125 + else: 1.126 + res.append('%s!=%s' % (self.name, val)) 1.127 + return ' '.join(res) 1.128 + 1.129 + 1.130 +class VersionFlag(object): 1.131 + ''' 1.132 + Class for version flags in manifest entries in the form: 1.133 + "flag=version" 1.134 + "flag<=version" 1.135 + "flag<version" 1.136 + "flag>=version" 1.137 + "flag>version" 1.138 + ''' 1.139 + def __init__(self, name): 1.140 + ''' 1.141 + Initialize a VersionFlag with the given name. 1.142 + ''' 1.143 + self.name = name 1.144 + self.values = [] 1.145 + 1.146 + def add_definition(self, definition): 1.147 + ''' 1.148 + Add a version flag definition. 1.149 + ''' 1.150 + assert(definition.startswith(self.name)) 1.151 + value = definition[len(self.name):] 1.152 + if value.startswith('='): 1.153 + self.values.append(('==', LooseVersion(value[1:]))) 1.154 + elif len(value) > 1 and value[0] in ['<', '>']: 1.155 + if value[1] == '=': 1.156 + if len(value) < 3: 1.157 + return errors.fatal('Malformed flag: %s' % definition) 1.158 + self.values.append((value[0:2], LooseVersion(value[2:]))) 1.159 + else: 1.160 + self.values.append((value[0], LooseVersion(value[1:]))) 1.161 + else: 1.162 + return errors.fatal('Malformed flag: %s' % definition) 1.163 + 1.164 + def matches(self, value): 1.165 + ''' 1.166 + Return whether one of the version flag definitions matches the given 1.167 + value. 1.168 + For example, 1.169 + flag = VersionFlag('foo') 1.170 + flag.add_definition('foo>=1.0') 1.171 + flag.matches('1.0') returns True 1.172 + flag.matches('1.1') returns True 1.173 + flag.matches('0.9') returns False 1.174 + flag = VersionFlag('foo') 1.175 + flag.add_definition('foo>=1.0') 1.176 + flag.add_definition('foo<0.5') 1.177 + flag.matches('0.4') returns True 1.178 + flag.matches('1.0') returns True 1.179 + flag.matches('0.6') returns False 1.180 + ''' 1.181 + value = LooseVersion(value) 1.182 + if not self.values: 1.183 + return True 1.184 + for comparison, val in self.values: 1.185 + if eval('value %s val' % comparison): 1.186 + return True 1.187 + return False 1.188 + 1.189 + def __str__(self): 1.190 + ''' 1.191 + Serialize the flag definitions in the same form given to each 1.192 + add_definition() call. 1.193 + ''' 1.194 + res = [] 1.195 + for comparison, val in self.values: 1.196 + if comparison == '==': 1.197 + res.append('%s=%s' % (self.name, val)) 1.198 + else: 1.199 + res.append('%s%s%s' % (self.name, comparison, val)) 1.200 + return ' '.join(res) 1.201 + 1.202 + 1.203 +class Flags(OrderedDict): 1.204 + ''' 1.205 + Class to handle a set of flags definitions given on a single manifest 1.206 + entry. 1.207 + ''' 1.208 + FLAGS = { 1.209 + 'application': StringFlag, 1.210 + 'appversion': VersionFlag, 1.211 + 'platformversion': VersionFlag, 1.212 + 'contentaccessible': Flag, 1.213 + 'os': StringFlag, 1.214 + 'osversion': VersionFlag, 1.215 + 'abi': StringFlag, 1.216 + 'platform': Flag, 1.217 + 'xpcnativewrappers': Flag, 1.218 + 'tablet': Flag, 1.219 + } 1.220 + RE = re.compile(r'([!<>=]+)') 1.221 + 1.222 + def __init__(self, *flags): 1.223 + ''' 1.224 + Initialize a set of flags given in string form. 1.225 + flags = Flags('contentaccessible=yes', 'appversion>=3.5') 1.226 + ''' 1.227 + OrderedDict.__init__(self) 1.228 + for f in flags: 1.229 + name = self.RE.split(f) 1.230 + name = name[0] 1.231 + if not name in self.FLAGS: 1.232 + errors.fatal('Unknown flag: %s' % name) 1.233 + continue 1.234 + if not name in self: 1.235 + self[name] = self.FLAGS[name](name) 1.236 + self[name].add_definition(f) 1.237 + 1.238 + def __str__(self): 1.239 + ''' 1.240 + Serialize the set of flags. 1.241 + ''' 1.242 + return ' '.join(str(self[k]) for k in self) 1.243 + 1.244 + def match(self, **filter): 1.245 + ''' 1.246 + Return whether the set of flags match the set of given filters. 1.247 + flags = Flags('contentaccessible=yes', 'appversion>=3.5', 1.248 + 'application=foo') 1.249 + flags.match(application='foo') returns True 1.250 + flags.match(application='foo', appversion='3.5') returns True 1.251 + flags.match(application='foo', appversion='3.0') returns False 1.252 + ''' 1.253 + for name, value in filter.iteritems(): 1.254 + if not name in self: 1.255 + continue 1.256 + if not self[name].matches(value): 1.257 + return False 1.258 + return True