Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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/.
5 import re
6 from distutils.version import LooseVersion
7 from mozpack.errors import errors
8 from collections import OrderedDict
11 class Flag(object):
12 '''
13 Class for flags in manifest entries in the form:
14 "flag" (same as "flag=true")
15 "flag=yes|true|1"
16 "flag=no|false|0"
17 '''
18 def __init__(self, name):
19 '''
20 Initialize a Flag with the given name.
21 '''
22 self.name = name
23 self.value = None
25 def add_definition(self, definition):
26 '''
27 Add a flag value definition. Replaces any previously set value.
28 '''
29 if definition == self.name:
30 self.value = True
31 return
32 assert(definition.startswith(self.name))
33 if definition[len(self.name)] != '=':
34 return errors.fatal('Malformed flag: %s' % definition)
35 value = definition[len(self.name) + 1:]
36 if value in ('yes', 'true', '1', 'no', 'false', '0'):
37 self.value = value
38 else:
39 return errors.fatal('Unknown value in: %s' % definition)
41 def matches(self, value):
42 '''
43 Return whether the flag value matches the given value. The values
44 are canonicalized for comparison.
45 '''
46 if value in ('yes', 'true', '1', True):
47 return self.value in ('yes', 'true', '1', True)
48 if value in ('no', 'false', '0', False):
49 return self.value in ('no', 'false', '0', False, None)
50 raise RuntimeError('Invalid value: %s' % value)
52 def __str__(self):
53 '''
54 Serialize the flag value in the same form given to the last
55 add_definition() call.
56 '''
57 if self.value is None:
58 return ''
59 if self.value is True:
60 return self.name
61 return '%s=%s' % (self.name, self.value)
64 class StringFlag(object):
65 '''
66 Class for string flags in manifest entries in the form:
67 "flag=string"
68 "flag!=string"
69 '''
70 def __init__(self, name):
71 '''
72 Initialize a StringFlag with the given name.
73 '''
74 self.name = name
75 self.values = []
77 def add_definition(self, definition):
78 '''
79 Add a string flag definition.
80 '''
81 assert(definition.startswith(self.name))
82 value = definition[len(self.name):]
83 if value.startswith('='):
84 self.values.append(('==', value[1:]))
85 elif value.startswith('!='):
86 self.values.append(('!=', value[2:]))
87 else:
88 return errors.fatal('Malformed flag: %s' % definition)
90 def matches(self, value):
91 '''
92 Return whether one of the string flag definitions matches the given
93 value.
94 For example,
95 flag = StringFlag('foo')
96 flag.add_definition('foo!=bar')
97 flag.matches('bar') returns False
98 flag.matches('qux') returns True
99 flag = StringFlag('foo')
100 flag.add_definition('foo=bar')
101 flag.add_definition('foo=baz')
102 flag.matches('bar') returns True
103 flag.matches('baz') returns True
104 flag.matches('qux') returns False
105 '''
106 if not self.values:
107 return True
108 for comparison, val in self.values:
109 if eval('value %s val' % comparison):
110 return True
111 return False
113 def __str__(self):
114 '''
115 Serialize the flag definitions in the same form given to each
116 add_definition() call.
117 '''
118 res = []
119 for comparison, val in self.values:
120 if comparison == '==':
121 res.append('%s=%s' % (self.name, val))
122 else:
123 res.append('%s!=%s' % (self.name, val))
124 return ' '.join(res)
127 class VersionFlag(object):
128 '''
129 Class for version flags in manifest entries in the form:
130 "flag=version"
131 "flag<=version"
132 "flag<version"
133 "flag>=version"
134 "flag>version"
135 '''
136 def __init__(self, name):
137 '''
138 Initialize a VersionFlag with the given name.
139 '''
140 self.name = name
141 self.values = []
143 def add_definition(self, definition):
144 '''
145 Add a version flag definition.
146 '''
147 assert(definition.startswith(self.name))
148 value = definition[len(self.name):]
149 if value.startswith('='):
150 self.values.append(('==', LooseVersion(value[1:])))
151 elif len(value) > 1 and value[0] in ['<', '>']:
152 if value[1] == '=':
153 if len(value) < 3:
154 return errors.fatal('Malformed flag: %s' % definition)
155 self.values.append((value[0:2], LooseVersion(value[2:])))
156 else:
157 self.values.append((value[0], LooseVersion(value[1:])))
158 else:
159 return errors.fatal('Malformed flag: %s' % definition)
161 def matches(self, value):
162 '''
163 Return whether one of the version flag definitions matches the given
164 value.
165 For example,
166 flag = VersionFlag('foo')
167 flag.add_definition('foo>=1.0')
168 flag.matches('1.0') returns True
169 flag.matches('1.1') returns True
170 flag.matches('0.9') returns False
171 flag = VersionFlag('foo')
172 flag.add_definition('foo>=1.0')
173 flag.add_definition('foo<0.5')
174 flag.matches('0.4') returns True
175 flag.matches('1.0') returns True
176 flag.matches('0.6') returns False
177 '''
178 value = LooseVersion(value)
179 if not self.values:
180 return True
181 for comparison, val in self.values:
182 if eval('value %s val' % comparison):
183 return True
184 return False
186 def __str__(self):
187 '''
188 Serialize the flag definitions in the same form given to each
189 add_definition() call.
190 '''
191 res = []
192 for comparison, val in self.values:
193 if comparison == '==':
194 res.append('%s=%s' % (self.name, val))
195 else:
196 res.append('%s%s%s' % (self.name, comparison, val))
197 return ' '.join(res)
200 class Flags(OrderedDict):
201 '''
202 Class to handle a set of flags definitions given on a single manifest
203 entry.
204 '''
205 FLAGS = {
206 'application': StringFlag,
207 'appversion': VersionFlag,
208 'platformversion': VersionFlag,
209 'contentaccessible': Flag,
210 'os': StringFlag,
211 'osversion': VersionFlag,
212 'abi': StringFlag,
213 'platform': Flag,
214 'xpcnativewrappers': Flag,
215 'tablet': Flag,
216 }
217 RE = re.compile(r'([!<>=]+)')
219 def __init__(self, *flags):
220 '''
221 Initialize a set of flags given in string form.
222 flags = Flags('contentaccessible=yes', 'appversion>=3.5')
223 '''
224 OrderedDict.__init__(self)
225 for f in flags:
226 name = self.RE.split(f)
227 name = name[0]
228 if not name in self.FLAGS:
229 errors.fatal('Unknown flag: %s' % name)
230 continue
231 if not name in self:
232 self[name] = self.FLAGS[name](name)
233 self[name].add_definition(f)
235 def __str__(self):
236 '''
237 Serialize the set of flags.
238 '''
239 return ' '.join(str(self[k]) for k in self)
241 def match(self, **filter):
242 '''
243 Return whether the set of flags match the set of given filters.
244 flags = Flags('contentaccessible=yes', 'appversion>=3.5',
245 'application=foo')
246 flags.match(application='foo') returns True
247 flags.match(application='foo', appversion='3.5') returns True
248 flags.match(application='foo', appversion='3.0') returns False
249 '''
250 for name, value in filter.iteritems():
251 if not name in self:
252 continue
253 if not self[name].matches(value):
254 return False
255 return True