From 3731e1d8f38be0ba3360d6badae789b1078e5b5a Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 18 Aug 2021 14:13:17 -0700 Subject: [PATCH] make EnvironmentVariablesObject a proper holder Currently, EnvironmentVariablesObject is a strange holder-that's-not-a-holder. This has implicaitons for things that expect to get an EnvironmentVariables object, as we can't automatically unholder it, and instead have to to manually do so. Now we can automatically unholder it, which makes everything much nicer. --- mesonbuild/interpreter/interpreter.py | 43 +++++++++---------- mesonbuild/interpreter/interpreterobjects.py | 44 +++++++------------- mesonbuild/interpreter/kwargs.py | 3 +- mesonbuild/interpreter/mesonmain.py | 15 +++---- 4 files changed, 42 insertions(+), 63 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ec5c75c4d..d640e6559 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -42,7 +42,6 @@ from .mesonmain import MesonMain from .dependencyfallbacks import DependencyFallbacksHolder from .interpreterobjects import ( SubprojectHolder, - EnvironmentVariablesObject, ConfigurationDataObject, Test, RunProcess, @@ -396,6 +395,7 @@ class Interpreter(InterpreterBase, HoldableObject): build.Data: OBJ.DataHolder, build.InstallDir: OBJ.InstallDirHolder, build.IncludeDirs: OBJ.IncludeDirsHolder, + build.EnvironmentVariables: OBJ.EnvironmentVariablesObject, compilers.RunResult: compilerOBJ.TryRunResultHolder, dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder, coredata.UserFeatureOption: OBJ.FeatureOptionHolder, @@ -1723,19 +1723,14 @@ This will become a hard error in the future.''' % kwargs['input'], location=self kwargs: 'kwargs.FuncTest') -> None: self.add_test(node, args, kwargs, True) - def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariablesObject, T.Dict[str, str], T.List[str]]) -> build.EnvironmentVariables: - envlist = kwargs.get('env', EnvironmentVariablesObject()) - if isinstance(envlist, EnvironmentVariablesObject): - env = envlist.vars - elif isinstance(envlist, dict): - FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject) - env = EnvironmentVariablesObject(envlist) - env = env.vars - else: - # Convert from array to environment object - env = EnvironmentVariablesObject(envlist) - env = env.vars - return env + def unpack_env_kwarg(self, kwargs: T.Union[build.EnvironmentVariables, T.Dict[str, 'TYPE_var'], T.List['TYPE_var'], str]) -> build.EnvironmentVariables: + envlist = kwargs.get('env') + if envlist is None: + return build.EnvironmentVariables() + msg = ENV_KW.validator(envlist) + if msg: + raise InvalidArguments(f'"env": {msg}') + return ENV_KW.convertor(envlist) def make_test(self, node: mparser.BaseNode, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], @@ -2361,17 +2356,17 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @noKwargs @noArgsFlattening - def func_environment(self, node, args, kwargs): - if len(args) > 1: - raise InterpreterException('environment takes only one optional positional arguments') - elif len(args) == 1: + @typed_pos_args('environment', optargs=[(str, list, dict)]) + def func_environment(self, node: mparser.FunctionNode, args: T.Tuple[T.Union[None, str, T.List['TYPE_var'], T.Dict[str, 'TYPE_var']]], + kwargs: 'TYPE_kwargs') -> build.EnvironmentVariables: + init = args[0] + if init is not None: FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject) - initial_values = args[0] - if not isinstance(initial_values, dict) and not isinstance(initial_values, list): - raise InterpreterException('environment first argument must be a dictionary or a list') - else: - initial_values = {} - return EnvironmentVariablesObject(initial_values, self.subproject) + msg = ENV_KW.validator(init) + if msg: + raise InvalidArguments(f'"environment": {msg}') + return ENV_KW.convertor(init) + return build.EnvironmentVariables() @typed_pos_args('join_paths', varargs=str, min_varargs=1) @noKwargs diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index c614f0979..53a0666ab 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -229,39 +229,19 @@ class RunProcess(MesonInterpreterObject): def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.stderr -# TODO: Parsing the initial values should be either done directly in the -# `Interpreter` or in `build.EnvironmentVariables`. This way, this class -# can be converted into a pure object holder. -class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObject): - # TODO: Move the type cheking for initial_values out of this class and replace T.Any - def __init__(self, initial_values: T.Optional[T.Any] = None, subproject: str = ''): - super().__init__(subproject=subproject) - self.vars = build.EnvironmentVariables() + +class EnvironmentVariablesObject(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject): + + def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'): + super().__init__(obj, interpreter) self.methods.update({'set': self.set_method, 'append': self.append_method, 'prepend': self.prepend_method, }) - if isinstance(initial_values, build.EnvironmentVariables): - self.vars = initial_values - elif isinstance(initial_values, dict): - for k, v in initial_values.items(): - self.set_method([k, v], {}) - elif initial_values is not None: - for e in mesonlib.listify(initial_values): - if not isinstance(e, str): - raise InterpreterException('Env var definition must be a list of strings.') - if '=' not in e: - raise InterpreterException('Env var definition must be of type key=val.') - (k, val) = e.split('=', 1) - k = k.strip() - val = val.strip() - if ' ' in k: - raise InterpreterException('Env var key must not have spaces in it.') - self.set_method([k, val], {}) def __repr__(self) -> str: repr_str = "<{0}: {1}>" - return repr_str.format(self.__class__.__name__, self.vars.envvars) + return repr_str.format(self.__class__.__name__, self.held_object.envvars) def unpack_separator(self, kwargs: T.Dict[str, T.Any]) -> str: separator = kwargs.get('separator', os.pathsep) @@ -270,9 +250,13 @@ class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObjec " argument needs to be a string.") return separator + def __deepcopy__(self, memo: T.Dict[str, object]) -> 'EnvironmentVariablesObject': + # Avoid trying to copy the intepreter + return EnvironmentVariablesObject(copy.deepcopy(self.held_object), self.interpreter) + def warn_if_has_name(self, name: str) -> None: # Multiple append/prepend operations was not supported until 0.58.0. - if self.vars.has_name(name): + if self.held_object.has_name(name): m = f'Overriding previous value of environment variable {name!r} with a new one' FeatureNew('0.58.0', m).use(self.subproject) @@ -282,7 +266,7 @@ class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObjec def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> None: name, values = args separator = self.unpack_separator(kwargs) - self.vars.set(name, values, separator) + self.held_object.set(name, values, separator) @stringArgs @permittedKwargs({'separator'}) @@ -291,7 +275,7 @@ class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObjec name, values = args separator = self.unpack_separator(kwargs) self.warn_if_has_name(name) - self.vars.append(name, values, separator) + self.held_object.append(name, values, separator) @stringArgs @permittedKwargs({'separator'}) @@ -300,7 +284,7 @@ class EnvironmentVariablesObject(MutableInterpreterObject, MesonInterpreterObjec name, values = args separator = self.unpack_separator(kwargs) self.warn_if_has_name(name) - self.vars.prepend(name, values, separator) + self.held_object.prepend(name, values, separator) class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject): diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 9eb02be57..317f7c639 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -11,7 +11,6 @@ from typing_extensions import TypedDict, Literal from .. import build from .. import coredata from ..mesonlib import MachineChoice, File, FileMode, FileOrString -from .interpreterobjects import EnvironmentVariablesObject class FuncAddProjectArgs(TypedDict): @@ -39,7 +38,7 @@ class BaseTest(TypedDict): workdir: T.Optional[str] depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]] priority: int - env: EnvironmentVariablesObject + env: build.EnvironmentVariables suite: T.List[str] diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index 5355c4760..9c4f50ada 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -7,13 +7,13 @@ from .. import mlog from ..mesonlib import MachineChoice, OptionKey from ..programs import OverrideProgram, ExternalProgram +from ..interpreter.type_checking import ENV_KW from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, typed_pos_args, permittedKwargs, noArgsFlattening, noPosargs, noKwargs, typed_kwargs, KwargInfo, MesonVersionString, InterpreterException) from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder, - CustomTargetHolder, CustomTargetIndexHolder, - EnvironmentVariablesObject) + CustomTargetHolder, CustomTargetIndexHolder) from .type_checking import NATIVE_KW, NoneType import typing as T @@ -415,9 +415,10 @@ class MesonMain(MesonInterpreterObject): @FeatureNew('add_devenv', '0.58.0') @noKwargs - @typed_pos_args('add_devenv', (str, list, dict, EnvironmentVariablesObject)) - def add_devenv_method(self, args: T.Union[str, list, dict, EnvironmentVariablesObject], kwargs: T.Dict[str, T.Any]) -> None: + @typed_pos_args('add_devenv', (str, list, dict, build.EnvironmentVariables)) + def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, build.EnvironmentVariables]], kwargs: T.Dict[str, T.Any]) -> None: env = args[0] - if isinstance(env, (str, list, dict)): - env = EnvironmentVariablesObject(env) - self.build.devenv.append(env.vars) + msg = ENV_KW.validator(env) + if msg: + raise build.InvalidArguments(f'"add_devenv": {msg}') + self.build.devenv.append(ENV_KW.convertor(env))