From abb9a4e96f6a79c74aa6e1958b6c7de79c07a13d Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Fri, 27 Apr 2018 00:33:20 +0200 Subject: [PATCH] Interpreter: don't flatten the arguments of various methods this fixes eg set_variable('foo', ['bar', 'baz']), which was previously erroring out complaining about the number of arguments. Closes #1481 --- mesonbuild/interpreter.py | 9 +++- mesonbuild/interpreterbase.py | 49 ++++++++++++------- mesonbuild/modules/python.py | 4 ++ mesonbuild/modules/unstable_icestorm.py | 3 +- .../common/198 args flattening/meson.build | 23 +++++++++ 5 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 test cases/common/198 args flattening/meson.build diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 85634193b..aa1272f94 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -25,7 +25,7 @@ from .mesonlib import FileMode, Popen_safe, listify, extract_as_list, has_path_s from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException from .interpreterbase import InterpreterBase -from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs, permittedKwargs, permittedMethodKwargs +from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, permittedMethodKwargs, noArgsFlattening from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler from .modules import ModuleReturnValue @@ -202,6 +202,7 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): return name, val, desc + @noArgsFlattening def set_method(self, args, kwargs): (name, val, desc) = self.validate_args(args, kwargs) self.held_object.values[name] = (val, desc) @@ -223,6 +224,7 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): def has_method(self, args, kwargs): return args[0] in self.held_object.values + @noArgsFlattening def get_method(self, args, kwargs): if len(args) < 1 or len(args) > 2: raise InterpreterException('Get method takes one or two arguments.') @@ -1309,6 +1311,8 @@ class ModuleHolder(InterpreterObject, ObjectHolder): raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name)) if method_name.startswith('_'): raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname)) + if not getattr(fn, 'no-args-flattening', False): + args = flatten(args) # This is not 100% reliable but we can't use hash() # because the Build object contains dicts and lists. num_targets = len(self.interpreter.build.targets) @@ -1507,6 +1511,7 @@ class MesonMain(InterpreterObject): def project_name_method(self, args, kwargs): return self.interpreter.active_projectname + @noArgsFlattening def get_cross_property_method(self, args, kwargs): if len(args) < 1 or len(args) > 2: raise InterpreterException('Must have one or two arguments.') @@ -3505,6 +3510,7 @@ This will become a hard error in the future.''') return self.subproject != '' @noKwargs + @noArgsFlattening def func_set_variable(self, node, args, kwargs): if len(args) != 2: raise InvalidCode('Set_variable takes two arguments.') @@ -3513,6 +3519,7 @@ This will become a hard error in the future.''') self.set_variable(varname, value) @noKwargs + @noArgsFlattening 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.') diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index f957d9070..09fdf9e49 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -31,6 +31,22 @@ def check_stringlist(a, msg='Arguments must be strings.'): mlog.debug('Element not a string:', str(a)) raise InvalidArguments(msg) +def flatten(args): + if isinstance(args, mparser.StringNode): + return args.value + if isinstance(args, (int, str, mesonlib.File, InterpreterObject)): + return args + result = [] + for a in args: + if isinstance(a, list): + rest = flatten(a) + result = result + rest + elif isinstance(a, mparser.StringNode): + result.append(a.value) + else: + result.append(a) + return result + def noPosargs(f): @wraps(f) def wrapped(self, node, args, kwargs): @@ -55,6 +71,10 @@ def stringArgs(f): return f(self, node, args, kwargs) return wrapped +def noArgsFlattening(f): + setattr(f, 'no-args-flattening', True) + return f + class permittedKwargs: def __init__(self, permitted): @@ -114,7 +134,10 @@ class InterpreterObject: def method_call(self, method_name, args, kwargs): if method_name in self.methods: - return self.methods[method_name](args, kwargs) + method = self.methods[method_name] + if not getattr(method, 'no-args-flattening', False): + args = flatten(args) + return method(args, kwargs) raise InvalidCode('Unknown method "%s" in object.' % method_name) class MutableInterpreterObject(InterpreterObject): @@ -474,7 +497,11 @@ The result of this is undefined and will become a hard error in a future Meson r if is_disabled(posargs, kwargs): return Disabler() if func_name in self.funcs: - return self.funcs[func_name](node, self.flatten(posargs), kwargs) + func = self.funcs[func_name] + if not getattr(func, 'no-args-flattening', False): + posargs = flatten(posargs) + + return func(node, posargs, kwargs) else: self.unknown_function_called(func_name) @@ -508,7 +535,7 @@ The result of this is undefined and will become a hard error in a future Meson r return Disabler() if method_name == 'extract_objects': self.validate_extraction(obj.held_object) - return obj.method_call(method_name, self.flatten(args), kwargs) + return obj.method_call(method_name, args, kwargs) def bool_method_call(self, obj, method_name, args): (posargs, kwargs) = self.reduce_arguments(args) @@ -668,22 +695,6 @@ The result of this is undefined and will become a hard error in a future Meson r self.argument_depth -= 1 return reduced_pos, reduced_kw - def flatten(self, args): - if isinstance(args, mparser.StringNode): - return args.value - if isinstance(args, (int, str, mesonlib.File, InterpreterObject)): - return args - result = [] - for a in args: - if isinstance(a, list): - rest = self.flatten(a) - result = result + rest - elif isinstance(a, mparser.StringNode): - result.append(a.value) - else: - result.append(a) - return result - def assignment(self, node): assert(isinstance(node, mparser.AssignmentNode)) if self.argument_depth != 0: diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 0e569a049..a70510903 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -25,6 +25,7 @@ from ..interpreterbase import ( InterpreterObject, InvalidArguments ) from ..interpreter import ExternalProgramHolder +from ..interpreterbase import flatten from ..build import known_shmod_kwargs from .. import mlog from ..environment import detect_cpu_family @@ -415,6 +416,9 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): except AttributeError: raise InvalidArguments('Python object does not have method %s.' % method_name) + if not getattr(fn, 'no-args-flattening', False): + args = flatten(args) + if method_name in ['extension_module', 'dependency', 'install_sources']: value = fn(self.interpreter, None, args, kwargs) return self.interpreter.holderify(value) diff --git a/mesonbuild/modules/unstable_icestorm.py b/mesonbuild/modules/unstable_icestorm.py index 55c647f4c..bf06314db 100644 --- a/mesonbuild/modules/unstable_icestorm.py +++ b/mesonbuild/modules/unstable_icestorm.py @@ -13,6 +13,7 @@ # limitations under the License. from .. import mesonlib +from ..interpreterbase import flatten from . import ExtensionModule @@ -42,7 +43,7 @@ class IceStormModule(ExtensionModule): kwarg_sources = kwargs.get('sources', []) if not isinstance(kwarg_sources, list): kwarg_sources = [kwarg_sources] - all_sources = interpreter.source_strings_to_files(interpreter.flatten(arg_sources + kwarg_sources)) + all_sources = interpreter.source_strings_to_files(flatten(arg_sources + kwarg_sources)) if 'constraint_file' not in kwargs: raise mesonlib.MesonException('Constraint file not specified.') diff --git a/test cases/common/198 args flattening/meson.build b/test cases/common/198 args flattening/meson.build new file mode 100644 index 000000000..1c7467d3f --- /dev/null +++ b/test cases/common/198 args flattening/meson.build @@ -0,0 +1,23 @@ +project('args flattening') + +arr = get_variable('does-not-exist', ['bar', 'baz']) + +assert(arr == ['bar', 'baz'], 'get_variable with array fallback is broken') + +set_variable('arr', ['bar', 'baz']) + +assert(arr == ['bar', 'baz'], 'set_variable(array) is broken') + +conf = configuration_data() + +conf.set('foo', ['bar', 'baz']) + +assert(conf.get('foo') == ['bar', 'baz'], 'configuration_data.set(array) is broken') + +arr = conf.get('does-not-exist', ['bar', 'baz']) + +assert(arr == ['bar', 'baz'], 'configuration_data.get with array fallback is broken') + +arr = meson.get_cross_property('does-not-exist', ['bar', 'baz']) + +assert(arr == ['bar', 'baz'], 'meson.get_cross_property with array fallback is broken')