Backed out 10 changesets (bug 1803810) for xpcshell failures on test_import_global...
[gecko.git] / media / libaom / cmakeparser.py
blobb93a313bce9b0038f049daee0fb17635b77a5dbc
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 from pyparsing import (CharsNotIn, Group, Forward, Literal, Suppress, Word,
5 QuotedString, ZeroOrMore, alphas, alphanums)
6 from string import Template
7 import re
9 # Grammar for CMake
10 comment = Literal('#') + ZeroOrMore(CharsNotIn('\n'))
11 quoted_argument = QuotedString('\"', '\\', multiline=True)
12 unquoted_argument = CharsNotIn('\n ()#\"\\')
13 argument = quoted_argument | unquoted_argument | Suppress(comment)
14 arguments = Forward()
15 arguments << (argument | (Literal('(') + ZeroOrMore(arguments) + Literal(')')))
16 identifier = Word(alphas, alphanums+'_')
17 command = Group(identifier + Literal('(') + ZeroOrMore(arguments) + Literal(')'))
18 file_elements = command | Suppress(comment)
19 cmake = ZeroOrMore(file_elements)
22 def extract_arguments(parsed):
23 """Extract the command arguments skipping the parentheses"""
24 return parsed[2:len(parsed) - 1]
27 def match_block(command, parsed, start):
28 """Find the end of block starting with the command"""
29 depth = 0
30 end = start + 1
31 endcommand = 'end' + command
32 while parsed[end][0] != endcommand or depth > 0:
33 if parsed[end][0] == command:
34 depth += 1
35 elif parsed[end][0] == endcommand:
36 depth -= 1
37 end = end + 1
38 if end == len(parsed):
39 print('error: eof when trying to match block statement: %s'
40 % parsed[start])
41 return end
44 def parse_if(parsed, start):
45 """Parse if/elseif/else/endif into a list of conditions and commands"""
46 depth = 0
47 conditions = []
48 condition = [extract_arguments(parsed[start])]
49 start = start + 1
50 end = start
52 while parsed[end][0] != 'endif' or depth > 0:
53 command = parsed[end][0]
54 if command == 'if':
55 depth += 1
56 elif command == 'else' and depth == 0:
57 condition.append(parsed[start:end])
58 conditions.append(condition)
59 start = end + 1
60 condition = [['TRUE']]
61 elif command == 'elseif' and depth == 0:
62 condition.append(parsed[start:end])
63 conditions.append(condition)
64 condition = [extract_arguments(parsed[end])]
65 start = end + 1
66 elif command == 'endif':
67 depth -= 1
68 end = end + 1
69 if end == len(parsed):
70 print('error: eof when trying to match if statement: %s'
71 % parsed[start])
72 condition.append(parsed[start:end])
73 conditions.append(condition)
74 return end, conditions
77 def substs(variables, values):
78 """Substitute variables into values"""
79 new_values = []
80 for value in values:
81 t = Template(value)
82 new_value = t.safe_substitute(variables)
84 # Safe substitute leaves unrecognized variables in place.
85 # We replace them with the empty string.
86 new_values.append(re.sub('\$\{\w+\}', '', new_value))
87 return new_values
90 def evaluate(variables, cache_variables, parsed):
91 """Evaluate a list of parsed commands, returning sources to build"""
92 i = 0
93 sources = []
94 while i < len(parsed):
95 command = parsed[i][0]
96 arguments = substs(variables, extract_arguments(parsed[i]))
98 if command == 'foreach':
99 end = match_block(command, parsed, i)
100 for argument in arguments[1:]:
101 # ; is also a valid divider, why have one when you can have two?
102 argument = argument.replace(';', ' ')
103 for value in argument.split():
104 variables[arguments[0]] = value
105 cont_eval, new_sources = evaluate(variables, cache_variables,
106 parsed[i+1:end])
107 sources.extend(new_sources)
108 if not cont_eval:
109 return cont_eval, sources
110 elif command == 'function':
111 # for now we just execute functions inline at point of declaration
112 # as this is sufficient to build libaom
113 pass
114 elif command == 'if':
115 i, conditions = parse_if(parsed, i)
116 for condition in conditions:
117 if evaluate_boolean(variables, condition[0]):
118 cont_eval, new_sources = evaluate(variables,
119 cache_variables,
120 condition[1])
121 sources.extend(new_sources)
122 if not cont_eval:
123 return cont_eval, sources
124 break
125 elif command == 'include':
126 if arguments:
127 try:
128 print('including: %s' % arguments[0])
129 sources.extend(parse(variables, cache_variables, arguments[0]))
130 except IOError:
131 print('warning: could not include: %s' % arguments[0])
132 elif command == 'list':
133 try:
134 action = arguments[0]
135 variable = arguments[1]
136 values = arguments[2:]
137 if action == 'APPEND':
138 if variable not in variables:
139 variables[variable] = ' '.join(values)
140 else:
141 variables[variable] += ' ' + ' '.join(values)
142 except (IndexError, KeyError):
143 pass
144 elif command == 'option':
145 variable = arguments[0]
146 value = arguments[2]
147 # Allow options to be override without changing CMake files
148 if variable not in variables:
149 variables[variable] = value
150 elif command == 'return':
151 return False, sources
152 elif command == 'set':
153 variable = arguments[0]
154 values = arguments[1:]
155 # CACHE variables are not set if already present
156 try:
157 cache = values.index('CACHE')
158 values = values[0:cache]
159 if variable not in variables:
160 variables[variable] = ' '.join(values)
161 cache_variables.append(variable)
162 except ValueError:
163 variables[variable] = ' '.join(values)
164 # we need to emulate the behavior of these function calls
165 # because we don't support interpreting them directly
166 # see bug 1492292
167 elif command in ['set_aom_config_var', 'set_aom_detect_var']:
168 variable = arguments[0]
169 value = arguments[1]
170 if variable not in variables:
171 variables[variable] = value
172 cache_variables.append(variable)
173 elif command == 'set_aom_option_var':
174 # option vars cannot go into cache_variables
175 variable = arguments[0]
176 value = arguments[2]
177 if variable not in variables:
178 variables[variable] = value
179 elif command == 'add_asm_library':
180 try:
181 sources.extend(variables[arguments[1]].split(' '))
182 except (IndexError, KeyError):
183 pass
184 elif command == 'add_intrinsics_object_library':
185 try:
186 sources.extend(variables[arguments[3]].split(' '))
187 except (IndexError, KeyError):
188 pass
189 elif command == 'add_library':
190 for source in arguments[1:]:
191 sources.extend(source.split(' '))
192 elif command == 'target_sources':
193 for source in arguments[1:]:
194 sources.extend(source.split(' '))
195 elif command == 'MOZDEBUG':
196 print('>>>> MOZDEBUG: %s' % ' '.join(arguments))
197 i += 1
198 return True, sources
201 def evaluate_boolean(variables, arguments):
202 """Evaluate a boolean expression"""
203 if not arguments:
204 return False
206 argument = arguments[0]
208 if argument == 'NOT':
209 return not evaluate_boolean(variables, arguments[1:])
211 if argument == '(':
212 i = 0
213 depth = 1
214 while depth > 0 and i < len(arguments):
215 i += 1
216 if arguments[i] == '(':
217 depth += 1
218 if arguments[i] == ')':
219 depth -= 1
220 return evaluate_boolean(variables, arguments[1:i])
222 def evaluate_constant(argument):
223 try:
224 as_int = int(argument)
225 if as_int != 0:
226 return True
227 else:
228 return False
229 except ValueError:
230 upper = argument.upper()
231 if upper in ['ON', 'YES', 'TRUE', 'Y']:
232 return True
233 elif upper in ['OFF', 'NO', 'FALSE', 'N', 'IGNORE', '', 'NOTFOUND']:
234 return False
235 elif upper.endswith('-NOTFOUND'):
236 return False
237 return None
239 def lookup_variable(argument):
240 # If statements can have old-style variables which are not demarcated
241 # like ${VARIABLE}. Attempt to look up the variable both ways.
242 try:
243 if re.search('\$\{\w+\}', argument):
244 try:
245 t = Template(argument)
246 value = t.substitute(variables)
247 try:
248 # Attempt an old-style variable lookup with the
249 # substituted value.
250 return variables[value]
251 except KeyError:
252 return value
253 except ValueError:
254 # TODO: CMake supports nesting, e.g. ${${foo}}
255 return None
256 else:
257 return variables[argument]
258 except KeyError:
259 return None
261 lhs = lookup_variable(argument)
262 if lhs is None:
263 # variable resolution failed, treat as string
264 lhs = argument
266 if len(arguments) > 1:
267 op = arguments[1]
268 if op == 'AND':
269 return evaluate_constant(lhs) and evaluate_boolean(variables, arguments[2:])
270 elif op == 'MATCHES':
271 rhs = lookup_variable(arguments[2])
272 if not rhs:
273 rhs = arguments[2]
274 return not re.match(rhs, lhs) is None
275 elif op == 'OR':
276 return evaluate_constant(lhs) or evaluate_boolean(variables, arguments[2:])
277 elif op == 'STREQUAL':
278 rhs = lookup_variable(arguments[2])
279 if not rhs:
280 rhs = arguments[2]
281 return lhs == rhs
282 else:
283 lhs = evaluate_constant(lhs)
284 if lhs is None:
285 lhs = lookup_variable(argument)
287 return lhs
290 def parse(variables, cache_variables, filename):
291 parsed = cmake.parseFile(filename)
292 cont_eval, sources = evaluate(variables, cache_variables, parsed)
293 return sources