|
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 distutils.version import LooseVersion |
|
7 from mozpack.errors import errors |
|
8 from collections import OrderedDict |
|
9 |
|
10 |
|
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 |
|
24 |
|
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) |
|
40 |
|
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) |
|
51 |
|
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) |
|
62 |
|
63 |
|
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 = [] |
|
76 |
|
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) |
|
89 |
|
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 |
|
112 |
|
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) |
|
125 |
|
126 |
|
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 = [] |
|
142 |
|
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) |
|
160 |
|
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 |
|
185 |
|
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) |
|
198 |
|
199 |
|
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'([!<>=]+)') |
|
218 |
|
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) |
|
234 |
|
235 def __str__(self): |
|
236 ''' |
|
237 Serialize the set of flags. |
|
238 ''' |
|
239 return ' '.join(str(self[k]) for k in self) |
|
240 |
|
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 |