From 66b32a45915238230557d8591e4521bdd7ecdb3b Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 16 Jun 2021 23:55:06 +0200 Subject: [PATCH] holders: Introduce HoldableObject --- mesonbuild/build.py | 30 ++++++++--------- mesonbuild/compilers/compilers.py | 7 ++-- mesonbuild/coredata.py | 4 ++- mesonbuild/dependencies/base.py | 4 +-- mesonbuild/envconfig.py | 4 +-- mesonbuild/interpreter/__init__.py | 2 +- mesonbuild/interpreter/interpreter.py | 2 +- mesonbuild/interpreter/interpreterobjects.py | 2 +- mesonbuild/interpreterbase/baseobjects.py | 34 +++++++++++++------- mesonbuild/mesonlib/universal.py | 8 ++++- mesonbuild/programs.py | 2 +- run_unittests.py | 21 ++++++++++-- 12 files changed, 78 insertions(+), 42 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 8724cab77..a9abcf94d 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -28,6 +28,7 @@ from . import dependencies from . import mlog from . import programs from .mesonlib import ( + HoldableObject, File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, extract_as_list, typeslistify, stringlistify, classify_unity_sources, get_filenames_templates_dict, substitute_values, has_path_sep, unholder, @@ -125,13 +126,13 @@ def get_target_macos_dylib_install_name(ld) -> str: class InvalidArguments(MesonException): pass -class DependencyOverride: +class DependencyOverride(HoldableObject): def __init__(self, dep, node, explicit=True): self.dep = dep self.node = node self.explicit = explicit -class Headers: +class Headers(HoldableObject): def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], install_dir: T.Optional[str], install_mode: T.Optional['FileMode'], @@ -161,7 +162,7 @@ class Headers: return self.custom_install_mode -class Man: +class Man(HoldableObject): def __init__(self, sources: T.List[File], install_dir: T.Optional[str], install_mode: T.Optional['FileMode'], subproject: str, @@ -182,7 +183,7 @@ class Man: return self.sources -class InstallDir: +class InstallDir(HoldableObject): def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str, install_mode: T.Optional['FileMode'], @@ -327,12 +328,11 @@ class Build: return link_args.get(compiler.get_language(), []) -class IncludeDirs: +class IncludeDirs(HoldableObject): """Internal representation of an include_directories call.""" - def __init__(self, curdir: str, dirs: T.List[str], is_system: bool, - extra_build_dirs: T.Optional[T.List[str]] = None): + def __init__(self, curdir: str, dirs: T.List[str], is_system: bool, extra_build_dirs: T.Optional[T.List[str]] = None): self.curdir = curdir self.incdirs = dirs self.is_system = is_system @@ -361,7 +361,7 @@ class IncludeDirs: strlist.append(os.path.join(sourcedir, self.curdir, idir)) return strlist -class ExtractedObjects: +class ExtractedObjects(HoldableObject): ''' Holds a list of sources for which the objects must be extracted ''' @@ -416,7 +416,7 @@ class ExtractedObjects: for source in self.srclist ] -class EnvironmentVariables: +class EnvironmentVariables(HoldableObject): def __init__(self) -> None: self.envvars = [] # The set of all env vars we have operations for. Only used for self.has_name() @@ -458,7 +458,7 @@ class EnvironmentVariables: env[name] = method(env, name, values, separator) return env -class Target: +class Target(HoldableObject): # TODO: should Target be an abc.ABCMeta? @@ -1508,7 +1508,7 @@ You probably should put it in link_with instead.''') 'platforms') return -class Generator: +class Generator(HoldableObject): def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], arguments: T.List[str], output: T.List[str], @@ -1584,7 +1584,7 @@ class Generator: return output -class GeneratedList: +class GeneratedList(HoldableObject): """The output of generator.process.""" @@ -2527,7 +2527,7 @@ class Jar(BuildTarget): return ['-cp', os.pathsep.join(cp_paths)] return [] -class CustomTargetIndex: +class CustomTargetIndex(HoldableObject): """A special opaque object returned by indexing a CustomTarget. This object exists in Meson, but acts as a proxy in the backends, making targets depend @@ -2583,7 +2583,7 @@ class CustomTargetIndex: def get_custom_install_dir(self): return self.target.get_custom_install_dir() -class ConfigurationData: +class ConfigurationData(HoldableObject): def __init__(self) -> None: super().__init__() self.values = {} # T.Dict[str, T.Union[str, int, bool]] @@ -2602,7 +2602,7 @@ class ConfigurationData: # A bit poorly named, but this represents plain data files to copy # during install. -class Data: +class Data(HoldableObject): def __init__(self, sources: T.List[File], install_dir: str, install_mode: T.Optional['FileMode'], subproject: str, rename: T.List[str] = None): diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 19288ebc3..efe521c5a 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -23,6 +23,7 @@ from .. import coredata from .. import mlog from .. import mesonlib from ..mesonlib import ( + HoldableObject, EnvironmentException, MachineChoice, MesonException, Popen_safe, LibType, TemporaryDirectoryWinProof, OptionKey, ) @@ -435,7 +436,7 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', class CrossNoRunException(MesonException): pass -class RunResult: +class RunResult(HoldableObject): def __init__(self, compiled: bool, returncode: int = 999, stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'): self.compiled = compiled @@ -444,7 +445,7 @@ class RunResult: self.stderr = stderr -class CompileResult: +class CompileResult(HoldableObject): """The result of Compiler.compiles (and friends).""" @@ -467,7 +468,7 @@ class CompileResult: self.text_mode = text_mode -class Compiler(metaclass=abc.ABCMeta): +class Compiler(HoldableObject, metaclass=abc.ABCMeta): # Libraries to ignore in find_library() since they are provided by the # compiler or the C library. Currently only used for MSVC. ignore_libs = [] # type: T.List[str] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 89cec46df..107e4b816 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -19,6 +19,7 @@ from itertools import chain from pathlib import PurePath from collections import OrderedDict from .mesonlib import ( + HoldableObject, MesonException, EnvironmentException, MachineChoice, PerMachine, PerMachineDefaultable, default_libdir, default_libexecdir, default_prefix, split_args, OptionKey, OptionType, stringlistify, @@ -61,7 +62,7 @@ class MesonVersionMismatchException(MesonException): self.current_version = current_version -class UserOption(T.Generic[_T]): +class UserOption(T.Generic[_T], HoldableObject): def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], yielding: T.Optional[bool]): super().__init__() self.choices = choices @@ -255,6 +256,7 @@ class UserFeatureOption(UserComboOption): def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None): super().__init__(description, self.static_choices, value, yielding) + self.name: T.Optional[str] = None # TODO: Refactor options to all store their name def is_enabled(self) -> bool: return self.value == 'enabled' diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 515edcfc1..e12c69782 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -22,7 +22,7 @@ from enum import Enum from .. import mlog from ..compilers import clib_langs -from ..mesonlib import MachineChoice, MesonException +from ..mesonlib import MachineChoice, MesonException, HoldableObject from ..mesonlib import version_compare_many from ..interpreterbase import FeatureDeprecated @@ -65,7 +65,7 @@ class DependencyMethods(Enum): DependencyTypeName = T.NewType('DependencyTypeName', str) -class Dependency: +class Dependency(HoldableObject): @classmethod def _process_include_type_kw(cls, kwargs: T.Dict[str, T.Any]) -> str: diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index a93905a3d..307aac30e 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -17,7 +17,7 @@ import typing as T from enum import Enum from . import mesonlib -from .mesonlib import EnvironmentException +from .mesonlib import EnvironmentException, HoldableObject from . import mlog from pathlib import Path @@ -232,7 +232,7 @@ class Properties: def get(self, key: str, default: T.Optional[T.Union[str, bool, int, T.List[str]]] = None) -> T.Optional[T.Union[str, bool, int, T.List[str]]]: return self.properties.get(key, default) -class MachineInfo: +class MachineInfo(HoldableObject): def __init__(self, system: str, cpu_family: str, cpu: str, endian: str): self.system = system self.cpu_family = cpu_family diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index 58ee729e8..62b09bf37 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -20,6 +20,6 @@ from .interpreter import Interpreter, permitted_dependency_kwargs from .compiler import CompilerHolder from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, CustomTargetIndexHolder, MachineHolder, Test, - ConfigurationDataHolder, SubprojectHolder, DependencyHolder, + ConfigurationDataObject, SubprojectHolder, DependencyHolder, GeneratedListHolder, ExternalProgramHolder, extract_required_kwarg) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index b4cdb571b..ea3628891 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -230,7 +230,7 @@ permitted_dependency_kwargs = { 'version', } -class Interpreter(InterpreterBase): +class Interpreter(InterpreterBase, HoldableObject): def __init__( self, diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index e27c1f772..db42c6701 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -24,7 +24,7 @@ from ..interpreterbase import (ContainerTypeInfo, KwargInfo, from ..interpreterbase.decorators import FeatureCheckBase from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..programs import ExternalProgram -from ..mesonlib import FileMode, OptionKey, listify, Popen_safe +from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe import typing as T diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index b08da2fd4..d910e51ae 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -15,24 +15,33 @@ from .. import mparser from .exceptions import InvalidCode from .helpers import flatten +from ..mesonlib import HoldableObject import typing as T +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ..interpreter import Interpreter + TV_fw_var = T.Union[str, int, bool, list, dict, 'InterpreterObject'] TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]] TV_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]] TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) -TYPE_elementary = T.Union[str, int, bool] -TYPE_var = T.Union[TYPE_elementary, T.List[T.Any], T.Dict[str, T.Any], 'InterpreterObject'] +TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any]] +TYPE_var = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject'] TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] +TYPE_kwargs = T.Dict[str, TYPE_var] TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] class InterpreterObject: def __init__(self, *, subproject: T.Optional[str] = None) -> None: - self.methods = {} # type: T.Dict[str, T.Callable[[T.List[TYPE_nvar], TYPE_nkwargs], TYPE_var]] + self.methods: T.Dict[ + str, + T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var] + ] = {} # Current node set during a method call. This can be used as location # when printing a warning message during a method call. self.current_node: mparser.BaseNode = None @@ -41,8 +50,8 @@ class InterpreterObject: def method_call( self, method_name: str, - args: TV_fw_args, - kwargs: TV_fw_kwargs + args: T.List[TYPE_var], + kwargs: TYPE_kwargs ) -> TYPE_var: if method_name in self.methods: method = self.methods[method_name] @@ -52,20 +61,23 @@ class InterpreterObject: raise InvalidCode('Unknown method "%s" in object.' % method_name) class MesonInterpreterObject(InterpreterObject): - ''' All non-elementary objects should be derived from this ''' + ''' All non-elementary objects and non-object-holders should be derived from this ''' class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' -TV_InterpreterObject = T.TypeVar('TV_InterpreterObject') +InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=HoldableObject) -class ObjectHolder(MesonInterpreterObject, T.Generic[TV_InterpreterObject]): - def __init__(self, obj: TV_InterpreterObject, *, subproject: T.Optional[str] = None) -> None: - super().__init__(subproject=subproject) +class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): + def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None: + super().__init__(subproject=interpreter.subproject) + assert isinstance(obj, HoldableObject), f'This is a bug: Trying to hold object of type `{type(obj).__name__}` that is not an `HoldableObject`' self.held_object = obj + self.interpreter = interpreter + self.env = self.interpreter.environment def __repr__(self) -> str: - return f'' + return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>' class RangeHolder(MesonInterpreterObject): def __init__(self, start: int, stop: int, step: int, *, subproject: T.Optional[str] = None) -> None: diff --git a/mesonbuild/mesonlib/universal.py b/mesonbuild/mesonlib/universal.py index 9daabee32..8dc16dc43 100644 --- a/mesonbuild/mesonlib/universal.py +++ b/mesonbuild/mesonlib/universal.py @@ -18,6 +18,7 @@ import enum import sys import stat import time +import abc import platform, subprocess, operator, os, shlex, shutil, re import collections from functools import lru_cache, wraps, total_ordering @@ -46,6 +47,7 @@ __all__ = [ 'an_unpicklable_object', 'python_command', 'project_meson_versions', + 'HoldableObject', 'File', 'FileMode', 'GitException', @@ -267,6 +269,10 @@ def check_direntry_issues(direntry_array: T.Union[T.List[T.Union[str, bytes]], s import threading an_unpicklable_object = threading.Lock() +class HoldableObject(metaclass=abc.ABCMeta): + ''' Dummy base class for all objects that can be + held by an interpreter.baseobjects.ObjectHolder ''' + class FileMode: # The first triad is for owner permissions, the second for group permissions, # and the third for others (everyone else). @@ -366,7 +372,7 @@ dot_C_dot_H_warning = """You are using .C or .H files in your project. This is d Visual Studio compiler, as it treats .C files as C code, unless you add the /TP compiler flag, but this is unreliable. See https://github.com/mesonbuild/meson/pull/8747 for the discussions.""" -class File: +class File(HoldableObject): def __init__(self, is_built: bool, subdir: str, fname: str): if fname.endswith(".C") or fname.endswith(".H"): mlog.warning(dot_C_dot_H_warning, once=True) diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index 1d93b8aa5..06af320f0 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -30,7 +30,7 @@ if T.TYPE_CHECKING: from .environment import Environment -class ExternalProgram: +class ExternalProgram(mesonlib.HoldableObject): """A program that is found on the system.""" diff --git a/run_unittests.py b/run_unittests.py index f81462fb0..067404f50 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -727,7 +727,15 @@ class InternalTests(unittest.TestCase): self.assertEqual([1, 2, 3], listify([1, [2, [3]]])) self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False)) # Test flattening and unholdering - holder1 = ObjectHolder(1) + class TestHeldObj(mesonbuild.mesonlib.HoldableObject): + def __init__(self, val: int) -> None: + self._val = val + class MockInterpreter: + def __init__(self) -> None: + self.subproject = '' + self.environment = None + heldObj1 = TestHeldObj(1) + holder1 = ObjectHolder(heldObj1, MockInterpreter()) self.assertEqual([holder1], listify(holder1)) self.assertEqual([holder1], listify([holder1])) self.assertEqual([holder1, 2], listify([holder1, 2])) @@ -753,8 +761,16 @@ class InternalTests(unittest.TestCase): self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True)) self.assertEqual(kwargs, {}) + class TestHeldObj(mesonbuild.mesonlib.HoldableObject): + pass + class MockInterpreter: + def __init__(self) -> None: + self.subproject = '' + self.environment = None + heldObj = TestHeldObj() + # Test unholding - holder3 = ObjectHolder(3) + holder3 = ObjectHolder(heldObj, MockInterpreter()) kwargs = {'sources': [1, 2, holder3]} self.assertEqual(kwargs, {'sources': [1, 2, holder3]}) @@ -769,7 +785,6 @@ class InternalTests(unittest.TestCase): _mock.pcdep = mock.Mock() _mock.pcdep.name = "some_name" _mock.version_reqs = [] - _mock = mock.Mock(held_object=_mock) # pkgconfig dependency as lib deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")