mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 15:28:39 +09:00
Add Python cmake parser
Unfortunately, upstream has removed the configure+make build system our import script relies on to generate code and moz.build files.
This commit is contained in:
parent
b9b9d8e4a1
commit
8411fe7c7d
2 changed files with 468 additions and 0 deletions
278
media/libaom/cmakeparser.py
Normal file
278
media/libaom/cmakeparser.py
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from pyparsing import (CharsNotIn, Group, Forward, Literal, Suppress, Word,
|
||||
QuotedString, ZeroOrMore, alphas, alphanums)
|
||||
from string import Template
|
||||
import re
|
||||
|
||||
# Grammar for CMake
|
||||
comment = Literal('#') + ZeroOrMore(CharsNotIn('\n'))
|
||||
quoted_argument = QuotedString('\"', '\\', multiline=True)
|
||||
unquoted_argument = CharsNotIn('\n ()#\"\\')
|
||||
argument = quoted_argument | unquoted_argument | Suppress(comment)
|
||||
arguments = Forward()
|
||||
arguments << (argument | (Literal('(') + ZeroOrMore(arguments) + Literal(')')))
|
||||
identifier = Word(alphas, alphanums+'_')
|
||||
command = Group(identifier + Literal('(') + ZeroOrMore(arguments) + Literal(')'))
|
||||
file_elements = command | Suppress(comment)
|
||||
cmake = ZeroOrMore(file_elements)
|
||||
|
||||
|
||||
def extract_arguments(parsed):
|
||||
"""Extract the command arguments skipping the parentheses"""
|
||||
return parsed[2:len(parsed) - 1]
|
||||
|
||||
|
||||
def match_block(command, parsed, start):
|
||||
"""Find the end of block starting with the command"""
|
||||
depth = 0
|
||||
end = start + 1
|
||||
endcommand = 'end' + command
|
||||
while parsed[end][0] != endcommand or depth > 0:
|
||||
if parsed[end][0] == command:
|
||||
depth += 1
|
||||
elif parsed[end][0] == endcommand:
|
||||
depth -= 1
|
||||
end = end + 1
|
||||
if end == len(parsed):
|
||||
print('error: eof when trying to match block statement: %s'
|
||||
% parsed[start])
|
||||
return end
|
||||
|
||||
|
||||
def parse_if(parsed, start):
|
||||
"""Parse if/elseif/else/endif into a list of conditions and commands"""
|
||||
depth = 0
|
||||
conditions = []
|
||||
condition = [extract_arguments(parsed[start])]
|
||||
start = start + 1
|
||||
end = start
|
||||
|
||||
while parsed[end][0] != 'endif' or depth > 0:
|
||||
command = parsed[end][0]
|
||||
if command == 'if':
|
||||
depth += 1
|
||||
elif command == 'else' and depth == 0:
|
||||
condition.append(parsed[start:end])
|
||||
conditions.append(condition)
|
||||
start = end + 1
|
||||
condition = [['TRUE']]
|
||||
elif command == 'elseif' and depth == 0:
|
||||
condition.append(parsed[start:end])
|
||||
conditions.append(condition)
|
||||
condition = [extract_arguments(parsed[end])]
|
||||
start = end + 1
|
||||
elif command == 'endif':
|
||||
depth -= 1
|
||||
end = end + 1
|
||||
if end == len(parsed):
|
||||
print('error: eof when trying to match if statement: %s'
|
||||
% parsed[start])
|
||||
condition.append(parsed[start:end])
|
||||
conditions.append(condition)
|
||||
return end, conditions
|
||||
|
||||
|
||||
def substs(variables, values):
|
||||
"""Substitute variables into values"""
|
||||
new_values = []
|
||||
for value in values:
|
||||
t = Template(value)
|
||||
new_value = t.safe_substitute(variables)
|
||||
|
||||
# Safe substitute leaves unrecognized variables in place.
|
||||
# We replace them with the empty string.
|
||||
new_values.append(re.sub('\$\{\w+\}', '', new_value))
|
||||
return new_values
|
||||
|
||||
|
||||
def evaluate(variables, cache_variables, parsed):
|
||||
"""Evaluate a list of parsed commands, returning sources to build"""
|
||||
i = 0
|
||||
sources = []
|
||||
while i < len(parsed):
|
||||
command = parsed[i][0]
|
||||
arguments = substs(variables, extract_arguments(parsed[i]))
|
||||
|
||||
if command == 'foreach':
|
||||
end = match_block(command, parsed, i)
|
||||
for argument in arguments[1:]:
|
||||
# ; is also a valid divider, why have one when you can have two?
|
||||
argument = argument.replace(';', ' ')
|
||||
for value in argument.split():
|
||||
variables[arguments[0]] = value
|
||||
cont_eval, new_sources = evaluate(variables, cache_variables,
|
||||
parsed[i+1:end])
|
||||
sources.extend(new_sources)
|
||||
if not cont_eval:
|
||||
return cont_eval, sources
|
||||
elif command == 'function':
|
||||
# for now we just execute functions inline at point of declaration
|
||||
# as this is sufficient to build libaom
|
||||
pass
|
||||
elif command == 'if':
|
||||
i, conditions = parse_if(parsed, i)
|
||||
for condition in conditions:
|
||||
if evaluate_boolean(variables, condition[0]):
|
||||
cont_eval, new_sources = evaluate(variables,
|
||||
cache_variables,
|
||||
condition[1])
|
||||
sources.extend(new_sources)
|
||||
if not cont_eval:
|
||||
return cont_eval, sources
|
||||
break
|
||||
elif command == 'include':
|
||||
if arguments:
|
||||
try:
|
||||
print('including: %s' % arguments[0])
|
||||
sources.extend(parse(variables, cache_variables, arguments[0]))
|
||||
except IOError:
|
||||
print('warning: could not include: %s' % arguments[0])
|
||||
elif command == 'list':
|
||||
try:
|
||||
action = arguments[0]
|
||||
variable = arguments[1]
|
||||
values = arguments[2:]
|
||||
if action == 'APPEND':
|
||||
if not variables.has_key(variable):
|
||||
variables[variable] = ' '.join(values)
|
||||
else:
|
||||
variables[variable] += ' ' + ' '.join(values)
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
elif command == 'option':
|
||||
variable = arguments[0]
|
||||
value = arguments[2]
|
||||
# Allow options to be override without changing CMake files
|
||||
if not variables.has_key(variable):
|
||||
variables[variable] = value
|
||||
elif command == 'return':
|
||||
return False, sources
|
||||
elif command == 'set':
|
||||
variable = arguments[0]
|
||||
values = arguments[1:]
|
||||
# CACHE variables are not set if already present
|
||||
try:
|
||||
cache = values.index('CACHE')
|
||||
values = values[0:cache]
|
||||
if not variables.has_key(variable):
|
||||
variables[variable] = ' '.join(values)
|
||||
cache_variables.append(variable)
|
||||
except ValueError:
|
||||
variables[variable] = ' '.join(values)
|
||||
elif command == 'add_asm_library':
|
||||
try:
|
||||
sources.extend(variables[arguments[1]].split(' '))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
elif command == 'add_intrinsics_object_library':
|
||||
try:
|
||||
sources.extend(variables[arguments[3]].split(' '))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
elif command == 'add_library':
|
||||
for source in arguments[1:]:
|
||||
sources.extend(source.split(' '))
|
||||
elif command == 'target_sources':
|
||||
for source in arguments[1:]:
|
||||
sources.extend(source.split(' '))
|
||||
elif command == 'MOZDEBUG':
|
||||
print('>>>> MOZDEBUG: %s' % ' '.join(arguments))
|
||||
i += 1
|
||||
return True, sources
|
||||
|
||||
|
||||
def evaluate_boolean(variables, arguments):
|
||||
"""Evaluate a boolean expression"""
|
||||
if not arguments:
|
||||
return False
|
||||
|
||||
argument = arguments[0]
|
||||
|
||||
if argument == 'NOT':
|
||||
return not evaluate_boolean(variables, arguments[1:])
|
||||
|
||||
if argument == '(':
|
||||
i = 0
|
||||
depth = 1
|
||||
while depth > 0 and i < len(arguments):
|
||||
i += 1
|
||||
if arguments[i] == '(':
|
||||
depth += 1
|
||||
if arguments[i] == ')':
|
||||
depth -= 1
|
||||
return evaluate_boolean(variables, arguments[1:i])
|
||||
|
||||
def evaluate_constant(argument):
|
||||
try:
|
||||
as_int = int(argument)
|
||||
if as_int != 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except ValueError:
|
||||
upper = argument.upper()
|
||||
if upper in ['ON', 'YES', 'TRUE', 'Y']:
|
||||
return True
|
||||
elif upper in ['OFF', 'NO', 'FALSE', 'N', 'IGNORE', '', 'NOTFOUND']:
|
||||
return False
|
||||
elif upper.endswith('-NOTFOUND'):
|
||||
return False
|
||||
return None
|
||||
|
||||
def lookup_variable(argument):
|
||||
# If statements can have old-style variables which are not demarcated
|
||||
# like ${VARIABLE}. Attempt to look up the variable both ways.
|
||||
try:
|
||||
if re.search('\$\{\w+\}', argument):
|
||||
try:
|
||||
t = Template(argument)
|
||||
value = t.substitute(variables)
|
||||
try:
|
||||
# Attempt an old-style variable lookup with the
|
||||
# substituted value.
|
||||
return variables[value]
|
||||
except KeyError:
|
||||
return value
|
||||
except ValueError:
|
||||
# TODO: CMake supports nesting, e.g. ${${foo}}
|
||||
return None
|
||||
else:
|
||||
return variables[argument]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
lhs = lookup_variable(argument)
|
||||
if lhs is None:
|
||||
# variable resolution failed, treat as string
|
||||
lhs = argument
|
||||
|
||||
if len(arguments) > 1:
|
||||
op = arguments[1]
|
||||
if op == 'AND':
|
||||
return evaluate_constant(lhs) and evaluate_boolean(variables, arguments[2:])
|
||||
elif op == 'MATCHES':
|
||||
rhs = lookup_variable(arguments[2])
|
||||
if not rhs:
|
||||
rhs = arguments[2]
|
||||
return not re.match(rhs, lhs) is None
|
||||
elif op == 'OR':
|
||||
return evaluate_constant(lhs) or evaluate_boolean(variables, arguments[2:])
|
||||
elif op == 'STREQUAL':
|
||||
rhs = lookup_variable(arguments[2])
|
||||
if not rhs:
|
||||
rhs = arguments[2]
|
||||
return lhs == rhs
|
||||
else:
|
||||
lhs = evaluate_constant(lhs)
|
||||
if lhs is None:
|
||||
lhs = lookup_variable(argument)
|
||||
|
||||
return lhs
|
||||
|
||||
|
||||
def parse(variables, cache_variables, filename):
|
||||
parsed = cmake.parseFile(filename)
|
||||
cont_eval, sources = evaluate(variables, cache_variables, parsed)
|
||||
return sources
|
||||
190
media/libaom/test_cmakeparser.py
Normal file
190
media/libaom/test_cmakeparser.py
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from pyparsing import ParseException
|
||||
import unittest
|
||||
|
||||
import cmakeparser as cp
|
||||
|
||||
class TestCMakeParser(unittest.TestCase):
|
||||
def test_arguments(self):
|
||||
self.assertEqual(cp.arguments.parseString('1').asList(), ['1'])
|
||||
self.assertEqual(cp.arguments.parseString('(1 2)').asList(),
|
||||
['(', '1', '2', ')'])
|
||||
|
||||
def test_command(self):
|
||||
self.assertEqual(cp.command.parseString('blah()').asList(),
|
||||
[['blah', '(', ')']])
|
||||
self.assertEqual(cp.command.parseString('blah(1)').asList(),
|
||||
[['blah', '(', '1', ')']])
|
||||
self.assertEqual(cp.command.parseString('blah(1 (2 3))').asList(),
|
||||
[['blah', '(', '1', '(', '2', '3', ')', ')']])
|
||||
|
||||
def test_evaluate_boolean(self):
|
||||
self.assertTrue(cp.evaluate_boolean({}, ['TRUE']))
|
||||
self.assertFalse(cp.evaluate_boolean({}, ['NOT', 'TRUE']))
|
||||
self.assertFalse(cp.evaluate_boolean({}, ['TRUE', 'AND', 'FALSE']))
|
||||
self.assertTrue(cp.evaluate_boolean({}, ['ABC', 'MATCHES', '^AB']))
|
||||
self.assertTrue(cp.evaluate_boolean({}, ['TRUE', 'OR', 'FALSE']))
|
||||
self.assertTrue(cp.evaluate_boolean({}, ['ABC', 'STREQUAL', 'ABC']))
|
||||
self.assertTrue(cp.evaluate_boolean({'ABC': '1'}, ['ABC']))
|
||||
self.assertFalse(cp.evaluate_boolean({'ABC': '0'}, ['${ABC}']))
|
||||
self.assertFalse(cp.evaluate_boolean({}, ['ABC']))
|
||||
self.assertFalse(cp.evaluate_boolean({}, ['${ABC}']))
|
||||
self.assertTrue(cp.evaluate_boolean({'YES_ABC': 1, 'VAL': 'ABC'},
|
||||
['YES_${VAL}']))
|
||||
self.assertTrue(cp.evaluate_boolean({'ABC': 'DEF', 'DEF': 1},
|
||||
['${ABC}']))
|
||||
self.assertTrue(cp.evaluate_boolean({}, ['FALSE', 'OR', '(', 'FALSE', 'OR', 'TRUE', ')']))
|
||||
self.assertFalse(cp.evaluate_boolean({}, ['FALSE', 'OR', '(', 'FALSE', 'AND', 'TRUE', ')']))
|
||||
|
||||
def test_foreach(self):
|
||||
s = """
|
||||
set(STUFF A B C D E F)
|
||||
foreach(item ${STUFF})
|
||||
set(YES_${item} 1)
|
||||
endforeach ()
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cp.evaluate(variables, [], parsed)
|
||||
for k in ['A', 'B', 'C', 'D', 'E', 'F']:
|
||||
self.assertEqual(variables['YES_%s' % k], '1')
|
||||
|
||||
s = """
|
||||
set(STUFF "A;B;C;D;E;F")
|
||||
foreach(item ${STUFF})
|
||||
set(${item} 1)
|
||||
endforeach ()
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cp.evaluate(variables, [], parsed)
|
||||
for k in ['A', 'B', 'C', 'D', 'E', 'F']:
|
||||
self.assertEqual(variables[k], '1')
|
||||
|
||||
s = """
|
||||
set(STUFF D E F)
|
||||
foreach(item A B C ${STUFF})
|
||||
set(${item} 1)
|
||||
endforeach ()
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cp.evaluate(variables, [], parsed)
|
||||
for k in ['A', 'B', 'C', 'D', 'E', 'F']:
|
||||
self.assertEqual(variables[k], '1')
|
||||
|
||||
def test_list(self):
|
||||
s = 'list(APPEND TEST 1 1 2 3 5 8 13)'
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cache_variables = []
|
||||
cp.evaluate(variables, cache_variables, parsed)
|
||||
self.assertEqual(variables['TEST'], '1 1 2 3 5 8 13')
|
||||
self.assertEqual(len(cache_variables), 0)
|
||||
|
||||
s = """
|
||||
set(TEST 1)
|
||||
list(APPEND TEST 1 2 3 5 8 13)
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cache_variables = []
|
||||
cp.evaluate(variables, cache_variables, parsed)
|
||||
self.assertEqual(variables['TEST'], '1 1 2 3 5 8 13')
|
||||
self.assertEqual(len(cache_variables), 0)
|
||||
|
||||
def test_malformed_input(self):
|
||||
self.assertEqual(len(cp.cmake.parseString('func ((A)')), 0)
|
||||
self.assertEqual(len(cp.cmake.parseString('cmd"arg"(arg2)')), 0)
|
||||
self.assertRaises(ParseException, cp.command.parseString, 'func ((A)')
|
||||
self.assertRaises(ParseException, cp.identifier.parseString,
|
||||
'-asdlf')
|
||||
self.assertEqual(cp.identifier.parseString('asd-lf')[0], 'asd')
|
||||
self.assertRaises(ParseException, cp.quoted_argument.parseString,
|
||||
'blah"')
|
||||
s = """
|
||||
" blah blah
|
||||
blah
|
||||
"""
|
||||
self.assertRaises(ParseException, cp.quoted_argument.parseString,
|
||||
s)
|
||||
self.assertRaises(ParseException, cp.quoted_argument.parseString,
|
||||
'asdlf')
|
||||
self.assertRaises(ParseException, cp.unquoted_argument.parseString,
|
||||
'#asdflkj')
|
||||
|
||||
def test_parse_if(self):
|
||||
s = """
|
||||
if (A)
|
||||
B()
|
||||
elseif (C)
|
||||
D()
|
||||
elseif (E)
|
||||
F()
|
||||
else ()
|
||||
H()
|
||||
endif ()
|
||||
I()
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
self.assertEqual(len(parsed), 10)
|
||||
end, conditions = cp.parse_if(parsed, 0)
|
||||
self.assertEqual(parsed[end][0], 'endif')
|
||||
self.assertEqual(len(conditions), 4)
|
||||
self.assertEqual(conditions[0][0], ['A'])
|
||||
self.assertEqual(conditions[0][1][0], parsed[1])
|
||||
self.assertEqual(conditions[1][0], ['C'])
|
||||
self.assertEqual(conditions[1][1][0], parsed[3])
|
||||
self.assertEqual(conditions[2][0], ['E'])
|
||||
self.assertEqual(conditions[2][1][0], parsed[5])
|
||||
self.assertEqual(conditions[3][0], ['TRUE'])
|
||||
self.assertEqual(conditions[3][1][0], parsed[7])
|
||||
|
||||
def test_return(self):
|
||||
s = """
|
||||
set(TEST 2)
|
||||
if (true)
|
||||
return()
|
||||
endif ()
|
||||
set(TEST 3)
|
||||
"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cache_variables = []
|
||||
cp.evaluate(variables, cache_variables, parsed)
|
||||
self.assertEqual(variables['TEST'], '2')
|
||||
self.assertEqual(len(cache_variables), 0)
|
||||
|
||||
def test_set(self):
|
||||
s = """set(TEST 2)"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cache_variables = []
|
||||
cp.evaluate(variables, cache_variables, parsed)
|
||||
self.assertEqual(variables['TEST'], '2')
|
||||
self.assertEqual(len(cache_variables), 0)
|
||||
|
||||
s = """set(TEST 3 CACHE "documentation")"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cache_variables = []
|
||||
cp.evaluate(variables, cache_variables, parsed)
|
||||
self.assertEqual(variables['TEST'], '3')
|
||||
self.assertTrue('TEST' in cache_variables)
|
||||
|
||||
s = """set(TEST A B C D)"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
variables = {}
|
||||
cp.evaluate(variables, [], parsed)
|
||||
self.assertEqual(variables['TEST'], 'A B C D')
|
||||
|
||||
s = """set(TEST ${TEST} E F G H)"""
|
||||
parsed = cp.cmake.parseString(s)
|
||||
cp.evaluate(variables, [], parsed)
|
||||
self.assertEqual(variables['TEST'], 'A B C D E F G H')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue