holders: Introduce HoldableObject

pull/8903/head
Daniel Mensinger 4 years ago
parent d601227cb2
commit 66b32a4591
  1. 30
      mesonbuild/build.py
  2. 7
      mesonbuild/compilers/compilers.py
  3. 4
      mesonbuild/coredata.py
  4. 4
      mesonbuild/dependencies/base.py
  5. 4
      mesonbuild/envconfig.py
  6. 2
      mesonbuild/interpreter/__init__.py
  7. 2
      mesonbuild/interpreter/interpreter.py
  8. 2
      mesonbuild/interpreter/interpreterobjects.py
  9. 34
      mesonbuild/interpreterbase/baseobjects.py
  10. 8
      mesonbuild/mesonlib/universal.py
  11. 2
      mesonbuild/programs.py
  12. 21
      run_unittests.py

@ -28,6 +28,7 @@ from . import dependencies
from . import mlog from . import mlog
from . import programs from . import programs
from .mesonlib import ( from .mesonlib import (
HoldableObject,
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources, extract_as_list, typeslistify, stringlistify, classify_unity_sources,
get_filenames_templates_dict, substitute_values, has_path_sep, unholder, 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): class InvalidArguments(MesonException):
pass pass
class DependencyOverride: class DependencyOverride(HoldableObject):
def __init__(self, dep, node, explicit=True): def __init__(self, dep, node, explicit=True):
self.dep = dep self.dep = dep
self.node = node self.node = node
self.explicit = explicit self.explicit = explicit
class Headers: class Headers(HoldableObject):
def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], def __init__(self, sources: T.List[File], install_subdir: T.Optional[str],
install_dir: T.Optional[str], install_mode: T.Optional['FileMode'], install_dir: T.Optional[str], install_mode: T.Optional['FileMode'],
@ -161,7 +162,7 @@ class Headers:
return self.custom_install_mode return self.custom_install_mode
class Man: class Man(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: T.Optional[str], def __init__(self, sources: T.List[File], install_dir: T.Optional[str],
install_mode: T.Optional['FileMode'], subproject: str, install_mode: T.Optional['FileMode'], subproject: str,
@ -182,7 +183,7 @@ class Man:
return self.sources return self.sources
class InstallDir: class InstallDir(HoldableObject):
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str, def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
install_mode: T.Optional['FileMode'], install_mode: T.Optional['FileMode'],
@ -327,12 +328,11 @@ class Build:
return link_args.get(compiler.get_language(), []) return link_args.get(compiler.get_language(), [])
class IncludeDirs: class IncludeDirs(HoldableObject):
"""Internal representation of an include_directories call.""" """Internal representation of an include_directories call."""
def __init__(self, curdir: str, dirs: T.List[str], is_system: bool, def __init__(self, curdir: str, dirs: T.List[str], is_system: bool, extra_build_dirs: T.Optional[T.List[str]] = None):
extra_build_dirs: T.Optional[T.List[str]] = None):
self.curdir = curdir self.curdir = curdir
self.incdirs = dirs self.incdirs = dirs
self.is_system = is_system self.is_system = is_system
@ -361,7 +361,7 @@ class IncludeDirs:
strlist.append(os.path.join(sourcedir, self.curdir, idir)) strlist.append(os.path.join(sourcedir, self.curdir, idir))
return strlist return strlist
class ExtractedObjects: class ExtractedObjects(HoldableObject):
''' '''
Holds a list of sources for which the objects must be extracted Holds a list of sources for which the objects must be extracted
''' '''
@ -416,7 +416,7 @@ class ExtractedObjects:
for source in self.srclist for source in self.srclist
] ]
class EnvironmentVariables: class EnvironmentVariables(HoldableObject):
def __init__(self) -> None: def __init__(self) -> None:
self.envvars = [] self.envvars = []
# The set of all env vars we have operations for. Only used for self.has_name() # 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) env[name] = method(env, name, values, separator)
return env return env
class Target: class Target(HoldableObject):
# TODO: should Target be an abc.ABCMeta? # TODO: should Target be an abc.ABCMeta?
@ -1508,7 +1508,7 @@ You probably should put it in link_with instead.''')
'platforms') 'platforms')
return return
class Generator: class Generator(HoldableObject):
def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], def __init__(self, exe: T.Union['Executable', programs.ExternalProgram],
arguments: T.List[str], arguments: T.List[str],
output: T.List[str], output: T.List[str],
@ -1584,7 +1584,7 @@ class Generator:
return output return output
class GeneratedList: class GeneratedList(HoldableObject):
"""The output of generator.process.""" """The output of generator.process."""
@ -2527,7 +2527,7 @@ class Jar(BuildTarget):
return ['-cp', os.pathsep.join(cp_paths)] return ['-cp', os.pathsep.join(cp_paths)]
return [] return []
class CustomTargetIndex: class CustomTargetIndex(HoldableObject):
"""A special opaque object returned by indexing a CustomTarget. This object """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 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): def get_custom_install_dir(self):
return self.target.get_custom_install_dir() return self.target.get_custom_install_dir()
class ConfigurationData: class ConfigurationData(HoldableObject):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.values = {} # T.Dict[str, T.Union[str, int, bool]] 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 # A bit poorly named, but this represents plain data files to copy
# during install. # during install.
class Data: class Data(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: str, def __init__(self, sources: T.List[File], install_dir: str,
install_mode: T.Optional['FileMode'], subproject: str, install_mode: T.Optional['FileMode'], subproject: str,
rename: T.List[str] = None): rename: T.List[str] = None):

@ -23,6 +23,7 @@ from .. import coredata
from .. import mlog from .. import mlog
from .. import mesonlib from .. import mesonlib
from ..mesonlib import ( from ..mesonlib import (
HoldableObject,
EnvironmentException, MachineChoice, MesonException, EnvironmentException, MachineChoice, MesonException,
Popen_safe, LibType, TemporaryDirectoryWinProof, OptionKey, Popen_safe, LibType, TemporaryDirectoryWinProof, OptionKey,
) )
@ -435,7 +436,7 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler',
class CrossNoRunException(MesonException): class CrossNoRunException(MesonException):
pass pass
class RunResult: class RunResult(HoldableObject):
def __init__(self, compiled: bool, returncode: int = 999, def __init__(self, compiled: bool, returncode: int = 999,
stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'): stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'):
self.compiled = compiled self.compiled = compiled
@ -444,7 +445,7 @@ class RunResult:
self.stderr = stderr self.stderr = stderr
class CompileResult: class CompileResult(HoldableObject):
"""The result of Compiler.compiles (and friends).""" """The result of Compiler.compiles (and friends)."""
@ -467,7 +468,7 @@ class CompileResult:
self.text_mode = text_mode 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 # Libraries to ignore in find_library() since they are provided by the
# compiler or the C library. Currently only used for MSVC. # compiler or the C library. Currently only used for MSVC.
ignore_libs = [] # type: T.List[str] ignore_libs = [] # type: T.List[str]

@ -19,6 +19,7 @@ from itertools import chain
from pathlib import PurePath from pathlib import PurePath
from collections import OrderedDict from collections import OrderedDict
from .mesonlib import ( from .mesonlib import (
HoldableObject,
MesonException, EnvironmentException, MachineChoice, PerMachine, MesonException, EnvironmentException, MachineChoice, PerMachine,
PerMachineDefaultable, default_libdir, default_libexecdir, PerMachineDefaultable, default_libdir, default_libexecdir,
default_prefix, split_args, OptionKey, OptionType, stringlistify, default_prefix, split_args, OptionKey, OptionType, stringlistify,
@ -61,7 +62,7 @@ class MesonVersionMismatchException(MesonException):
self.current_version = current_version 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]): def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], yielding: T.Optional[bool]):
super().__init__() super().__init__()
self.choices = choices self.choices = choices
@ -255,6 +256,7 @@ class UserFeatureOption(UserComboOption):
def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None): def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
super().__init__(description, self.static_choices, value, yielding) 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: def is_enabled(self) -> bool:
return self.value == 'enabled' return self.value == 'enabled'

@ -22,7 +22,7 @@ from enum import Enum
from .. import mlog from .. import mlog
from ..compilers import clib_langs from ..compilers import clib_langs
from ..mesonlib import MachineChoice, MesonException from ..mesonlib import MachineChoice, MesonException, HoldableObject
from ..mesonlib import version_compare_many from ..mesonlib import version_compare_many
from ..interpreterbase import FeatureDeprecated from ..interpreterbase import FeatureDeprecated
@ -65,7 +65,7 @@ class DependencyMethods(Enum):
DependencyTypeName = T.NewType('DependencyTypeName', str) DependencyTypeName = T.NewType('DependencyTypeName', str)
class Dependency: class Dependency(HoldableObject):
@classmethod @classmethod
def _process_include_type_kw(cls, kwargs: T.Dict[str, T.Any]) -> str: def _process_include_type_kw(cls, kwargs: T.Dict[str, T.Any]) -> str:

@ -17,7 +17,7 @@ import typing as T
from enum import Enum from enum import Enum
from . import mesonlib from . import mesonlib
from .mesonlib import EnvironmentException from .mesonlib import EnvironmentException, HoldableObject
from . import mlog from . import mlog
from pathlib import Path 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]]]: 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) return self.properties.get(key, default)
class MachineInfo: class MachineInfo(HoldableObject):
def __init__(self, system: str, cpu_family: str, cpu: str, endian: str): def __init__(self, system: str, cpu_family: str, cpu: str, endian: str):
self.system = system self.system = system
self.cpu_family = cpu_family self.cpu_family = cpu_family

@ -20,6 +20,6 @@ from .interpreter import Interpreter, permitted_dependency_kwargs
from .compiler import CompilerHolder from .compiler import CompilerHolder
from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder,
CustomTargetIndexHolder, MachineHolder, Test, CustomTargetIndexHolder, MachineHolder, Test,
ConfigurationDataHolder, SubprojectHolder, DependencyHolder, ConfigurationDataObject, SubprojectHolder, DependencyHolder,
GeneratedListHolder, ExternalProgramHolder, GeneratedListHolder, ExternalProgramHolder,
extract_required_kwarg) extract_required_kwarg)

@ -230,7 +230,7 @@ permitted_dependency_kwargs = {
'version', 'version',
} }
class Interpreter(InterpreterBase): class Interpreter(InterpreterBase, HoldableObject):
def __init__( def __init__(
self, self,

@ -24,7 +24,7 @@ from ..interpreterbase import (ContainerTypeInfo, KwargInfo,
from ..interpreterbase.decorators import FeatureCheckBase from ..interpreterbase.decorators import FeatureCheckBase
from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram 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 import typing as T

@ -15,24 +15,33 @@
from .. import mparser from .. import mparser
from .exceptions import InvalidCode from .exceptions import InvalidCode
from .helpers import flatten from .helpers import flatten
from ..mesonlib import HoldableObject
import typing as T 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_var = T.Union[str, int, bool, list, dict, 'InterpreterObject']
TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]] 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_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]]
TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any])
TYPE_elementary = T.Union[str, int, bool] TYPE_elementary = T.Union[str, int, bool, T.List[T.Any], T.Dict[str, T.Any]]
TYPE_var = T.Union[TYPE_elementary, T.List[T.Any], T.Dict[str, T.Any], 'InterpreterObject'] TYPE_var = T.Union[TYPE_elementary, HoldableObject, 'MesonInterpreterObject']
TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode]
TYPE_kwargs = T.Dict[str, TYPE_var]
TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_nkwargs = T.Dict[str, TYPE_nvar]
TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str]
class InterpreterObject: class InterpreterObject:
def __init__(self, *, subproject: T.Optional[str] = None) -> None: 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 # Current node set during a method call. This can be used as location
# when printing a warning message during a method call. # when printing a warning message during a method call.
self.current_node: mparser.BaseNode = None self.current_node: mparser.BaseNode = None
@ -41,8 +50,8 @@ class InterpreterObject:
def method_call( def method_call(
self, self,
method_name: str, method_name: str,
args: TV_fw_args, args: T.List[TYPE_var],
kwargs: TV_fw_kwargs kwargs: TYPE_kwargs
) -> TYPE_var: ) -> TYPE_var:
if method_name in self.methods: if method_name in self.methods:
method = self.methods[method_name] method = self.methods[method_name]
@ -52,20 +61,23 @@ class InterpreterObject:
raise InvalidCode('Unknown method "%s" in object.' % method_name) raise InvalidCode('Unknown method "%s" in object.' % method_name)
class MesonInterpreterObject(InterpreterObject): 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: class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable ''' ''' 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]): class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]):
def __init__(self, obj: TV_InterpreterObject, *, subproject: T.Optional[str] = None) -> None: def __init__(self, obj: InterpreterObjectTypeVar, interpreter: 'Interpreter') -> None:
super().__init__(subproject=subproject) 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.held_object = obj
self.interpreter = interpreter
self.env = self.interpreter.environment
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Holder: {self.held_object!r}>' return f'<[{type(self).__name__}] holds [{type(self.held_object).__name__}]: {self.held_object!r}>'
class RangeHolder(MesonInterpreterObject): class RangeHolder(MesonInterpreterObject):
def __init__(self, start: int, stop: int, step: int, *, subproject: T.Optional[str] = None) -> None: def __init__(self, start: int, stop: int, step: int, *, subproject: T.Optional[str] = None) -> None:

@ -18,6 +18,7 @@ import enum
import sys import sys
import stat import stat
import time import time
import abc
import platform, subprocess, operator, os, shlex, shutil, re import platform, subprocess, operator, os, shlex, shutil, re
import collections import collections
from functools import lru_cache, wraps, total_ordering from functools import lru_cache, wraps, total_ordering
@ -46,6 +47,7 @@ __all__ = [
'an_unpicklable_object', 'an_unpicklable_object',
'python_command', 'python_command',
'project_meson_versions', 'project_meson_versions',
'HoldableObject',
'File', 'File',
'FileMode', 'FileMode',
'GitException', 'GitException',
@ -267,6 +269,10 @@ def check_direntry_issues(direntry_array: T.Union[T.List[T.Union[str, bytes]], s
import threading import threading
an_unpicklable_object = threading.Lock() 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: class FileMode:
# The first triad is for owner permissions, the second for group permissions, # The first triad is for owner permissions, the second for group permissions,
# and the third for others (everyone else). # 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 Visual Studio compiler, as it treats .C files as C code, unless you add
the /TP compiler flag, but this is unreliable. the /TP compiler flag, but this is unreliable.
See https://github.com/mesonbuild/meson/pull/8747 for the discussions.""" 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): def __init__(self, is_built: bool, subdir: str, fname: str):
if fname.endswith(".C") or fname.endswith(".H"): if fname.endswith(".C") or fname.endswith(".H"):
mlog.warning(dot_C_dot_H_warning, once=True) mlog.warning(dot_C_dot_H_warning, once=True)

@ -30,7 +30,7 @@ if T.TYPE_CHECKING:
from .environment import Environment from .environment import Environment
class ExternalProgram: class ExternalProgram(mesonlib.HoldableObject):
"""A program that is found on the system.""" """A program that is found on the system."""

@ -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]]]))
self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False)) self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
# Test flattening and unholdering # 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], listify([holder1])) self.assertEqual([holder1], listify([holder1]))
self.assertEqual([holder1, 2], listify([holder1, 2])) 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([1, 2, 3], extract(kwargs, 'sources', pop=True))
self.assertEqual(kwargs, {}) self.assertEqual(kwargs, {})
class TestHeldObj(mesonbuild.mesonlib.HoldableObject):
pass
class MockInterpreter:
def __init__(self) -> None:
self.subproject = ''
self.environment = None
heldObj = TestHeldObj()
# Test unholding # Test unholding
holder3 = ObjectHolder(3) holder3 = ObjectHolder(heldObj, MockInterpreter())
kwargs = {'sources': [1, 2, holder3]} kwargs = {'sources': [1, 2, holder3]}
self.assertEqual(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 = mock.Mock()
_mock.pcdep.name = "some_name" _mock.pcdep.name = "some_name"
_mock.version_reqs = [] _mock.version_reqs = []
_mock = mock.Mock(held_object=_mock)
# pkgconfig dependency as lib # pkgconfig dependency as lib
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib") deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")

Loading…
Cancel
Save