diff --git a/mesonast.py b/mesonast.py index 8618f5a0c..7f5fd90fe 100755 --- a/mesonast.py +++ b/mesonast.py @@ -25,7 +25,12 @@ import mesonbuild.astinterpreter +import sys + if __name__ == '__main__': - source_root = 'test cases/common/1 trivial' + if len(sys.argv) == 1: + source_root = 'test cases/common/1 trivial' + else: + source_root = sys.argv[1] ast = mesonbuild.astinterpreter.AstInterpreter(source_root, '') ast.dump() diff --git a/mesonbuild/astinterpreter.py b/mesonbuild/astinterpreter.py index d0d55910d..571ecfdd5 100644 --- a/mesonbuild/astinterpreter.py +++ b/mesonbuild/astinterpreter.py @@ -15,20 +15,87 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from . import interpreterbase, mlog +from . import interpreterbase, mlog, mparser, mesonlib +from . import environment + +from .interpreterbase import InterpreterException + +import os + +class DontCareObject(interpreterbase.InterpreterObject): + pass class MockExecutable(interpreterbase.InterpreterObject): pass +class MockStaticLibrary(interpreterbase.InterpreterObject): + pass + +class MockSharedLibrary(interpreterbase.InterpreterObject): + pass + class AstInterpreter(interpreterbase.InterpreterBase): def __init__(self, source_root, subdir): super().__init__(source_root, subdir) - self.funcs.update({'executable': self.func_executable, + self.funcs.update({'project' : self.func_do_nothing, + 'test' : self.func_do_nothing, + 'install_headers' : self.func_do_nothing, + 'install_man' : self.func_do_nothing, + 'install_data' : self.func_do_nothing, + 'configuration_data' : self.func_do_nothing, + 'configure_file' : self.func_do_nothing, + 'find_program' : self.func_do_nothing, + 'files' : self.func_files, + 'executable': self.func_executable, + 'static_library' : self.func_static_lib, + 'shared_library' : self.func_shared_lib, + 'build_target' : self.func_build_target, + 'subdir' : self.func_subdir, + 'set_variable' : self.func_set_variable, + 'get_variable' : self.func_get_variable, + 'is_variable' : self.func_is_variable, }) + def func_do_nothing(self, *args, **kwargs): + return DontCareObject() + def func_executable(self, *args, **kwargs): return MockExecutable() + def func_static_lib(self, *args, **kwargs): + return MockStaticLibrary() + + def func_shared_lib(self, *args, **kwargs): + return MockSharedLibrary() + + def func_subdir(self, node, args, kwargs): + prev_subdir = self.subdir + subdir = os.path.join(prev_subdir, args[0]) + self.subdir = subdir + buildfilename = os.path.join(self.subdir, environment.build_filename) + absname = os.path.join(self.source_root, buildfilename) + if not os.path.isfile(absname): + self.subdir = prev_subdir + raise InterpreterException('Nonexistant build def file %s.' % buildfilename) + with open(absname, encoding='utf8') as f: + code = f.read() + assert(isinstance(code, str)) + try: + codeblock = mparser.Parser(code).parse() + except mesonlib.MesonException as me: + me.file = buildfilename + raise me + self.evaluate_codeblock(codeblock) + self.subdir = prev_subdir + + def func_files(self, node, args, kwargs): + if not isinstance(args, list): + return [args] + return args + + def method_call(self, node): + return DontCareObject() + def dump(self): self.load_root_meson_file() self.sanity_check_ast() diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index a0eaf2745..cc75848f1 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -23,50 +23,16 @@ from . import compilers from .wrap import wrap from . import mesonlib from mesonbuild.interpreterbase import InterpreterBase +from mesonbuild.interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs from mesonbuild.interpreterbase import InterpreterException, InvalidArguments, InvalidCode from mesonbuild.interpreterbase import InterpreterObject, MutableInterpreterObject import os, sys, subprocess, shutil, uuid, re -from functools import wraps import importlib run_depr_printed = False -# Decorators for method calls. - -def check_stringlist(a, msg='Arguments must be strings.'): - if not isinstance(a, list): - mlog.debug('Not a list:', str(a)) - raise InvalidArguments('Argument not a list.') - if not all(isinstance(s, str) for s in a): - mlog.debug('Element not a string:', str(a)) - raise InvalidArguments(msg) - -def noPosargs(f): - @wraps(f) - def wrapped(self, node, args, kwargs): - if len(args) != 0: - raise InvalidArguments('Function does not take positional arguments.') - return f(self, node, args, kwargs) - return wrapped - -def noKwargs(f): - @wraps(f) - def wrapped(self, node, args, kwargs): - if len(kwargs) != 0: - raise InvalidArguments('Function does not take keyword arguments.') - return f(self, node, args, kwargs) - return wrapped - -def stringArgs(f): - @wraps(f) - def wrapped(self, node, args, kwargs): - assert(isinstance(args, list)) - check_stringlist(args) - return f(self, node, args, kwargs) - return wrapped - def stringifyUserArguments(args): if isinstance(args, list): return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args]) @@ -76,6 +42,7 @@ def stringifyUserArguments(args): return "'%s'" % args raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') + class TryRunResultHolder(InterpreterObject): def __init__(self, res): super().__init__() @@ -1275,36 +1242,6 @@ class Interpreter(InterpreterBase): except KeyError as e: pass - def func_set_variable(self, node, args, kwargs): - if len(args) != 2: - raise InvalidCode('Set_variable takes two arguments.') - varname = args[0] - value = self.to_native(args[1]) - self.set_variable(varname, value) - - @noKwargs - def func_get_variable(self, node, args, kwargs): - if len(args)<1 or len(args)>2: - raise InvalidCode('Get_variable takes one or two arguments.') - varname = args[0] - if not isinstance(varname, str): - raise InterpreterException('First argument must be a string.') - try: - return self.variables[varname] - except KeyError: - pass - if len(args) == 2: - return args[1] - raise InterpreterException('Tried to get unknown variable "%s".' % varname) - - @stringArgs - @noKwargs - def func_is_variable(self, node, args, kwargs): - if len(args) != 1: - raise InvalidCode('Is_variable takes two arguments.') - varname = args[0] - return varname in self.variables - @stringArgs @noKwargs def func_import(self, node, args, kwargs): @@ -2510,18 +2447,6 @@ requirements use the version keyword argument instead.''') pass return False - def evaluate_if(self, node): - assert(isinstance(node, mparser.IfClauseNode)) - for i in node.ifs: - result = self.evaluate_statement(i.condition) - if not(isinstance(result, bool)): - raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result)) - if result: - self.evaluate_codeblock(i.block) - return - if not isinstance(node.elseblock, mparser.EmptyNode): - self.evaluate_codeblock(node.elseblock) - def evaluate_ternary(self, node): assert(isinstance(node, mparser.TernaryNode)) result = self.evaluate_statement(node.condition) @@ -2638,14 +2563,6 @@ requirements use the version keyword argument instead.''') raise InterpreterException('Second argument to "or" is not a boolean.') return r - def evaluate_notstatement(self, cur): - v = self.evaluate_statement(cur.value) - if isinstance(v, mparser.BooleanNode): - v = v.value - if not isinstance(v, bool): - raise InterpreterException('Argument to "not" is not a boolean.') - return not v - def evaluate_uminusstatement(self, cur): v = self.evaluate_statement(cur.value) if isinstance(v, mparser.NumberNode): diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 7e732be5a..42685976f 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -19,6 +19,42 @@ from . import mparser, mesonlib, mlog from . import environment, dependencies import os, copy, re +from functools import wraps + +# Decorators for method calls. + +def check_stringlist(a, msg='Arguments must be strings.'): + if not isinstance(a, list): + mlog.debug('Not a list:', str(a)) + raise InvalidArguments('Argument not a list.') + if not all(isinstance(s, str) for s in a): + mlog.debug('Element not a string:', str(a)) + raise InvalidArguments(msg) + +def noPosargs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + if len(args) != 0: + raise InvalidArguments('Function does not take positional arguments.') + return f(self, node, args, kwargs) + return wrapped + +def noKwargs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + if len(kwargs) != 0: + raise InvalidArguments('Function does not take keyword arguments.') + return f(self, node, args, kwargs) + return wrapped + +def stringArgs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + assert(isinstance(args, list)) + check_stringlist(args) + return f(self, node, args, kwargs) + return wrapped + class InterpreterException(mesonlib.MesonException): pass @@ -159,6 +195,26 @@ class InterpreterBase: raise InvalidCode('Keyword arguments are invalid in array construction.') return arguments + def evaluate_notstatement(self, cur): + v = self.evaluate_statement(cur.value) + if isinstance(v, mparser.BooleanNode): + v = v.value + if not isinstance(v, bool): + raise InterpreterException('Argument to "not" is not a boolean.') + return not v + + + def evaluate_if(self, node): + assert(isinstance(node, mparser.IfClauseNode)) + for i in node.ifs: + result = self.evaluate_statement(i.condition) + if not(isinstance(result, bool)): + raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result)) + if result: + self.evaluate_codeblock(i.block) + return + if not isinstance(node.elseblock, mparser.EmptyNode): + self.evaluate_codeblock(node.elseblock) def function_call(self, node): func_name = node.func_name @@ -247,3 +303,49 @@ class InterpreterBase: return isinstance(value, (InterpreterObject, dependencies.Dependency, str, int, list, mesonlib.File)) + def func_build_target(self, node, args, kwargs): + if 'target_type' not in kwargs: + raise InterpreterException('Missing target_type keyword argument') + target_type = kwargs.pop('target_type') + if target_type == 'executable': + return self.func_executable(node, args, kwargs) + elif target_type == 'shared_library': + return self.func_shared_lib(node, args, kwargs) + elif target_type == 'static_library': + return self.func_static_lib(node, args, kwargs) + elif target_type == 'library': + return self.func_library(node, args, kwargs) + elif target_type == 'jar': + return self.func_jar(node, args, kwargs) + else: + raise InterpreterException('Unknown target_type.') + + def func_set_variable(self, node, args, kwargs): + if len(args) != 2: + raise InvalidCode('Set_variable takes two arguments.') + varname = args[0] + value = self.to_native(args[1]) + self.set_variable(varname, value) + +# @noKwargs + def func_get_variable(self, node, args, kwargs): + if len(args)<1 or len(args)>2: + raise InvalidCode('Get_variable takes one or two arguments.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('First argument must be a string.') + try: + return self.variables[varname] + except KeyError: + pass + if len(args) == 2: + return args[1] + raise InterpreterException('Tried to get unknown variable "%s".' % varname) + + @stringArgs + @noKwargs + def func_is_variable(self, node, args, kwargs): + if len(args) != 1: + raise InvalidCode('Is_variable takes two arguments.') + varname = args[0] + return varname in self.variables