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
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
)
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"""
31 endcommand
= 'end' + command
32 while parsed
[end
][0] != endcommand
or depth
> 0:
33 if parsed
[end
][0] == command
:
35 elif parsed
[end
][0] == endcommand
:
38 if end
== len(parsed
):
39 print('error: eof when trying to match block statement: %s'
44 def parse_if(parsed
, start
):
45 """Parse if/elseif/else/endif into a list of conditions and commands"""
48 condition
= [extract_arguments(parsed
[start
])]
52 while parsed
[end
][0] != 'endif' or depth
> 0:
53 command
= parsed
[end
][0]
56 elif command
== 'else' and depth
== 0:
57 condition
.append(parsed
[start
:end
])
58 conditions
.append(condition
)
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
])]
66 elif command
== 'endif':
69 if end
== len(parsed
):
70 print('error: eof when trying to match if statement: %s'
72 condition
.append(parsed
[start
:end
])
73 conditions
.append(condition
)
74 return end
, conditions
77 def substs(variables
, values
):
78 """Substitute variables into values"""
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
))
90 def evaluate(variables
, cache_variables
, parsed
):
91 """Evaluate a list of parsed commands, returning sources to build"""
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
,
107 sources
.extend(new_sources
)
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
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
,
121 sources
.extend(new_sources
)
123 return cont_eval
, sources
125 elif command
== 'include':
128 print('including: %s' % arguments
[0])
129 sources
.extend(parse(variables
, cache_variables
, arguments
[0]))
131 print('warning: could not include: %s' % arguments
[0])
132 elif command
== 'list':
134 action
= arguments
[0]
135 variable
= arguments
[1]
136 values
= arguments
[2:]
137 if action
== 'APPEND':
138 if not variables
.has_key(variable
):
139 variables
[variable
] = ' '.join(values
)
141 variables
[variable
] += ' ' + ' '.join(values
)
142 except (IndexError, KeyError):
144 elif command
== 'option':
145 variable
= arguments
[0]
147 # Allow options to be override without changing CMake files
148 if not variables
.has_key(variable
):
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
157 cache
= values
.index('CACHE')
158 values
= values
[0:cache
]
159 if not variables
.has_key(variable
):
160 variables
[variable
] = ' '.join(values
)
161 cache_variables
.append(variable
)
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
167 elif command
in ['set_aom_config_var', 'set_aom_detect_var']:
168 variable
= arguments
[0]
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]
177 if variable
not in variables
:
178 variables
[variable
] = value
179 elif command
== 'add_asm_library':
181 sources
.extend(variables
[arguments
[1]].split(' '))
182 except (IndexError, KeyError):
184 elif command
== 'add_intrinsics_object_library':
186 sources
.extend(variables
[arguments
[3]].split(' '))
187 except (IndexError, KeyError):
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
))
201 def evaluate_boolean(variables
, arguments
):
202 """Evaluate a boolean expression"""
206 argument
= arguments
[0]
208 if argument
== 'NOT':
209 return not evaluate_boolean(variables
, arguments
[1:])
214 while depth
> 0 and i
< len(arguments
):
216 if arguments
[i
] == '(':
218 if arguments
[i
] == ')':
220 return evaluate_boolean(variables
, arguments
[1:i
])
222 def evaluate_constant(argument
):
224 as_int
= int(argument
)
230 upper
= argument
.upper()
231 if upper
in ['ON', 'YES', 'TRUE', 'Y']:
233 elif upper
in ['OFF', 'NO', 'FALSE', 'N', 'IGNORE', '', 'NOTFOUND']:
235 elif upper
.endswith('-NOTFOUND'):
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.
243 if re
.search('\$\{\w+\}', argument
):
245 t
= Template(argument
)
246 value
= t
.substitute(variables
)
248 # Attempt an old-style variable lookup with the
250 return variables
[value
]
254 # TODO: CMake supports nesting, e.g. ${${foo}}
257 return variables
[argument
]
261 lhs
= lookup_variable(argument
)
263 # variable resolution failed, treat as string
266 if len(arguments
) > 1:
269 return evaluate_constant(lhs
) and evaluate_boolean(variables
, arguments
[2:])
270 elif op
== 'MATCHES':
271 rhs
= lookup_variable(arguments
[2])
274 return not re
.match(rhs
, lhs
) is None
276 return evaluate_constant(lhs
) or evaluate_boolean(variables
, arguments
[2:])
277 elif op
== 'STREQUAL':
278 rhs
= lookup_variable(arguments
[2])
283 lhs
= evaluate_constant(lhs
)
285 lhs
= lookup_variable(argument
)
290 def parse(variables
, cache_variables
, filename
):
291 parsed
= cmake
.parseFile(filename
)
292 cont_eval
, sources
= evaluate(variables
, cache_variables
, parsed
)