diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 89febcb05..957b259e7 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -13,6 +13,7 @@ # limitations under the License. from collections import OrderedDict +from dataclasses import dataclass, InitVar from functools import lru_cache from itertools import chain from pathlib import Path @@ -61,11 +62,11 @@ if T.TYPE_CHECKING: # Assembly files cannot be unitified and neither can LLVM IR files LANGS_CANT_UNITY = ('d', 'fortran', 'vala') +@dataclass(eq=False) class RegenInfo: - def __init__(self, source_dir: str, build_dir: str, depfiles: T.List[str]): - self.source_dir = source_dir - self.build_dir = build_dir - self.depfiles = depfiles + source_dir: str + build_dir: str + depfiles: T.List[str] class TestProtocol(enum.Enum): @@ -97,28 +98,30 @@ class TestProtocol(enum.Enum): return 'tap' +@dataclass(eq=False) class CleanTrees: ''' Directories outputted by custom targets that have to be manually cleaned because on Linux `ninja clean` only deletes empty directories. ''' - def __init__(self, build_dir: str, trees: T.List[str]): - self.build_dir = build_dir - self.trees = trees + build_dir: str + trees: T.List[str] +@dataclass(eq=False) class InstallData: - def __init__(self, source_dir: str, build_dir: str, prefix: str, libdir: str, - strip_bin: T.List[str], install_umask: T.Union[str, int], - mesonintrospect: T.List[str], version: str, - is_cross_build: bool): - # TODO: in python 3.8 or with typing_Extensions install_umask could be: - # `T.Union[T.Literal['preserve'], int]`, which would be more accurate. - self.source_dir = source_dir - self.build_dir = build_dir - self.prefix = prefix - self.libdir = libdir - self.strip_bin = strip_bin - self.install_umask = install_umask + source_dir: str + build_dir: str + prefix: str + libdir: str + strip_bin: T.List[str] + # TODO: in python 3.8 or with typing_Extensions this could be: + # `T.Union[T.Literal['preserve'], int]`, which would be more accurate. + install_umask: T.Union[str, int] + mesonintrospect: T.List[str] + version: str + is_cross_build: bool + + def __post_init__(self) -> None: self.targets: T.List[TargetInstallData] = [] self.headers: T.List[InstallDataBase] = [] self.man: T.List[InstallDataBase] = [] @@ -127,59 +130,52 @@ class InstallData: self.symlinks: T.List[InstallSymlinkData] = [] self.install_scripts: T.List[ExecutableSerialisation] = [] self.install_subdirs: T.List[SubdirInstallData] = [] - self.mesonintrospect = mesonintrospect - self.version = version - self.is_cross_build = is_cross_build +@dataclass(eq=False) class TargetInstallData: - + fname: str + outdir: str + outdir_name: InitVar[str] + aliases: T.Dict[str, str] + strip: bool + install_name_mappings: T.Mapping[str, str] + rpath_dirs_to_remove: T.Set[bytes] + install_rpath: str # TODO: install_mode should just always be a FileMode object + install_mode: T.Optional['FileMode'] + subproject: str + optional: bool = False + tag: T.Optional[str] = None - def __init__(self, fname: str, outdir: str, outdir_name: str, aliases: T.Dict[str, str], - strip: bool, install_name_mappings: T.Mapping[str, str], rpath_dirs_to_remove: T.Set[bytes], - install_rpath: str, install_mode: T.Optional['FileMode'], - subproject: str, optional: bool = False, tag: T.Optional[str] = None): - self.fname = fname - self.outdir = outdir - self.out_name = os.path.join(outdir_name, os.path.basename(fname)) - self.aliases = aliases - self.strip = strip - self.install_name_mappings = install_name_mappings - self.rpath_dirs_to_remove = rpath_dirs_to_remove - self.install_rpath = install_rpath - self.install_mode = install_mode - self.subproject = subproject - self.optional = optional - self.tag = tag + def __post_init__(self, outdir_name: str) -> None: + self.out_name = os.path.join(outdir_name, os.path.basename(self.fname)) +@dataclass(eq=False) class InstallEmptyDir: - def __init__(self, path: str, install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None): - self.path = path - self.install_mode = install_mode - self.subproject = subproject - self.tag = tag + path: str + install_mode: 'FileMode' + subproject: str + tag: T.Optional[str] = None +@dataclass(eq=False) class InstallDataBase: - def __init__(self, path: str, install_path: str, install_path_name: str, - install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None, - data_type: T.Optional[str] = None): - self.path = path - self.install_path = install_path - self.install_path_name = install_path_name - self.install_mode = install_mode - self.subproject = subproject - self.tag = tag - self.data_type = data_type - + path: str + install_path: str + install_path_name: str + install_mode: 'FileMode' + subproject: str + tag: T.Optional[str] = None + data_type: T.Optional[str] = None + +@dataclass(eq=False) class InstallSymlinkData: - def __init__(self, target: str, name: str, install_path: str, - subproject: str, tag: T.Optional[str] = None): - self.target = target - self.name = name - self.install_path = install_path - self.subproject = subproject - self.tag = tag + target: str + name: str + install_path: str + subproject: str + tag: T.Optional[str] = None +# cannot use dataclass here because "exclude" is out of order class SubdirInstallData(InstallDataBase): def __init__(self, path: str, install_path: str, install_path_name: str, install_mode: 'FileMode', exclude: T.Tuple[T.Set[str], T.Set[str]], @@ -187,64 +183,53 @@ class SubdirInstallData(InstallDataBase): super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type) self.exclude = exclude +@dataclass(eq=False) class ExecutableSerialisation: # XXX: should capture and feed default to False, instead of None? - def __init__(self, cmd_args: T.List[str], - env: T.Optional[build.EnvironmentVariables] = None, - exe_wrapper: T.Optional['programs.ExternalProgram'] = None, - workdir: T.Optional[str] = None, - extra_paths: T.Optional[T.List] = None, - capture: T.Optional[bool] = None, - feed: T.Optional[bool] = None, - tag: T.Optional[str] = None, - verbose: bool = False, - ) -> None: - self.cmd_args = cmd_args - self.env = env - if exe_wrapper is not None: - assert isinstance(exe_wrapper, programs.ExternalProgram) - self.exe_wrapper = exe_wrapper - self.workdir = workdir - self.extra_paths = extra_paths - self.capture = capture - self.feed = feed + cmd_args: T.List[str] + env: T.Optional[build.EnvironmentVariables] = None + exe_wrapper: T.Optional['programs.ExternalProgram'] = None + workdir: T.Optional[str] = None + extra_paths: T.Optional[T.List] = None + capture: T.Optional[bool] = None + feed: T.Optional[bool] = None + tag: T.Optional[str] = None + verbose: bool = False + + def __post_init__(self) -> None: + if self.exe_wrapper is not None: + assert isinstance(self.exe_wrapper, programs.ExternalProgram) self.pickled = False self.skip_if_destdir = False - self.verbose = verbose self.subproject = '' - self.tag = tag +@dataclass(eq=False) class TestSerialisation: - def __init__(self, name: str, project_name: str, suite: T.List[str], fname: T.List[str], - is_cross_built: bool, exe_wrapper: T.Optional[programs.ExternalProgram], - needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str], - env: build.EnvironmentVariables, should_fail: bool, - timeout: T.Optional[int], workdir: T.Optional[str], - extra_paths: T.List[str], protocol: TestProtocol, priority: int, - cmd_is_built: bool, depends: T.List[str], version: str): - self.name = name - self.project_name = project_name - self.suite = suite - self.fname = fname - self.is_cross_built = is_cross_built - if exe_wrapper is not None: - assert isinstance(exe_wrapper, programs.ExternalProgram) - self.exe_wrapper = exe_wrapper - self.is_parallel = is_parallel - self.cmd_args = cmd_args - self.env = env - self.should_fail = should_fail - self.timeout = timeout - self.workdir = workdir - self.extra_paths = extra_paths - self.protocol = protocol - self.priority = priority - self.needs_exe_wrapper = needs_exe_wrapper - self.cmd_is_built = cmd_is_built - self.depends = depends - self.version = version + name: str + project_name: str + suite: T.List[str] + fname: T.List[str] + is_cross_built: bool + exe_wrapper: T.Optional[programs.ExternalProgram] + needs_exe_wrapper: bool + is_parallel: bool + cmd_args: T.List[str] + env: build.EnvironmentVariables + should_fail: bool + timeout: T.Optional[int] + workdir: T.Optional[str] + extra_paths: T.List[str] + protocol: TestProtocol + priority: int + cmd_is_built: bool + depends: T.List[str] + version: str + + def __post_init__(self) -> None: + if self.exe_wrapper is not None: + assert isinstance(self.exe_wrapper, programs.ExternalProgram) def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index d8002bff3..93bf29d9d 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -13,6 +13,7 @@ # limitations under the License. from collections import OrderedDict +from dataclasses import dataclass, field from functools import lru_cache import copy import hashlib @@ -130,22 +131,19 @@ def get_target_macos_dylib_install_name(ld) -> str: class InvalidArguments(MesonException): pass +@dataclass(eq=False) class DependencyOverride(HoldableObject): - def __init__(self, dep: dependencies.Dependency, node: 'BaseNode', explicit: bool = True): - self.dep = dep - self.node = node - self.explicit = explicit + dep: dependencies.Dependency + node: 'BaseNode' + explicit: bool = True +@dataclass(eq=False) class Headers(HoldableObject): - - def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], - custom_install_dir: T.Optional[str], custom_install_mode: 'FileMode', - subproject: str): - self.sources = sources - self.install_subdir = install_subdir - self.custom_install_dir = custom_install_dir - self.custom_install_mode = custom_install_mode - self.subproject = subproject + sources: T.List[File] + install_subdir: T.Optional[str] + custom_install_dir: T.Optional[str] + custom_install_mode: 'FileMode' + subproject: str # TODO: we really don't need any of these methods, but they're preserved to # keep APIs relying on them working. @@ -166,16 +164,13 @@ class Headers(HoldableObject): return self.custom_install_mode +@dataclass(eq=False) class Man(HoldableObject): - - def __init__(self, sources: T.List[File], custom_install_dir: T.Optional[str], - custom_install_mode: 'FileMode', subproject: str, - locale: T.Optional[str]): - self.sources = sources - self.custom_install_dir = custom_install_dir - self.custom_install_mode = custom_install_mode - self.subproject = subproject - self.locale = locale + sources: T.List[File] + custom_install_dir: T.Optional[str] + custom_install_mode: 'FileMode' + subproject: str + locale: T.Optional[str] def get_custom_install_dir(self) -> T.Optional[str]: return self.custom_install_dir @@ -187,40 +182,30 @@ class Man(HoldableObject): return self.sources +@dataclass(eq=False) class EmptyDir(HoldableObject): - - def __init__(self, path: str, install_mode: 'FileMode', subproject: str, - install_tag: T.Optional[str] = None): - self.path = path - self.install_mode = install_mode - self.subproject = subproject - self.install_tag = install_tag + path: str + install_mode: 'FileMode' + subproject: str + install_tag: T.Optional[str] = None +@dataclass(eq=False) class InstallDir(HoldableObject): + source_subdir: str + installable_subdir: str + install_dir: str + install_mode: 'FileMode' + exclude: T.Tuple[T.Set[str], T.Set[str]] + strip_directory: bool + subproject: str + from_source_dir: bool = True + install_tag: T.Optional[str] = None - def __init__(self, source_subdir: str, installable_subdir: str, install_dir: str, - install_mode: 'FileMode', - exclude: T.Tuple[T.Set[str], T.Set[str]], - strip_directory: bool, subproject: str, - from_source_dir: bool = True, - install_tag: T.Optional[str] = None): - self.source_subdir = source_subdir - self.installable_subdir = installable_subdir - self.install_dir = install_dir - self.install_mode = install_mode - self.exclude = exclude - self.strip_directory = strip_directory - self.from_source_dir = from_source_dir - self.subproject = subproject - self.install_tag = install_tag - - +@dataclass(eq=False) class DepManifest: - - def __init__(self, version: str, license: T.List[str]): - self.version = version - self.license = license + version: str + license: T.List[str] def to_json(self) -> T.Dict[str, T.Union[str, T.List[str]]]: return { @@ -229,6 +214,7 @@ class DepManifest: } +# literally everything isn't dataclass stuff class Build: """A class that holds the status of one build including all dependencies and so on. @@ -363,18 +349,17 @@ class Build: return link_args.get(compiler.get_language(), []) +@dataclass(eq=False) class IncludeDirs(HoldableObject): """Internal representation of an include_directories call.""" - def __init__(self, curdir: str, incdirs: T.List[str], is_system: bool, extra_build_dirs: T.Optional[T.List[str]] = None): - self.curdir = curdir - self.incdirs = incdirs - self.is_system = is_system - - # Interpreter has validated that all given directories - # actually exist. - self.extra_build_dirs: T.List[str] = extra_build_dirs or [] + curdir: str + incdirs: T.List[str] + is_system: bool + # Interpreter has validated that all given directories + # actually exist. + extra_build_dirs: T.List[str] = field(default_factory=list) def __repr__(self) -> str: r = '<{} {}/{}>' @@ -404,21 +389,18 @@ class IncludeDirs(HoldableObject): strlist.append(os.path.join(builddir, self.curdir, idir)) return strlist +@dataclass(eq=False) class ExtractedObjects(HoldableObject): ''' Holds a list of sources for which the objects must be extracted ''' - def __init__(self, target: 'BuildTarget', - srclist: T.Optional[T.List[File]] = None, - genlist: T.Optional[T.List['GeneratedTypes']] = None, - objlist: T.Optional[T.List[T.Union[str, 'File', 'ExtractedObjects']]] = None, - recursive: bool = True): - self.target = target - self.recursive = recursive - self.srclist: T.List[File] = srclist if srclist is not None else [] - self.genlist: T.List['GeneratedTypes'] = genlist if genlist is not None else [] - self.objlist: T.Optional[T.List[T.Union[str, 'File', 'ExtractedObjects']]] = \ - objlist if objlist is not None else [] + target: 'BuildTarget' + srclist: T.List[File] = field(default_factory=list) + genlist: T.List['GeneratedTypes'] = field(default_factory=list) + objlist: T.List[T.Union[str, 'File', 'ExtractedObjects']] = field(default_factory=list) + recursive: bool = True + + def __post_init__(self) -> None: if self.target.is_unity: self.check_unity_compatible() @@ -523,23 +505,25 @@ class EnvironmentVariables(HoldableObject): env[name] = method(env, name, values, separator) return env +@dataclass(eq=False) class Target(HoldableObject): # TODO: should Target be an abc.ABCMeta? - def __init__(self, name: str, subdir: str, subproject: str, build_by_default: bool, for_machine: MachineChoice): - if has_path_sep(name): + name: str + subdir: str + subproject: str + build_by_default: bool + for_machine: MachineChoice + + def __post_init__(self) -> None: + if has_path_sep(self.name): # Fix failing test 53 when this becomes an error. mlog.warning(textwrap.dedent(f'''\ - Target "{name}" has a path separator in its name. + Target "{self.name}" has a path separator in its name. This is not supported, it can cause unexpected failures and will become a hard error in the future.\ ''')) - self.name = name - self.subdir = subdir - self.subproject = subproject - self.build_by_default = build_by_default - self.for_machine = for_machine self.install = False self.build_always_stale = False self.option_overrides_base: T.Dict[OptionKey, str] = {} @@ -548,6 +532,7 @@ class Target(HoldableObject): if not hasattr(self, 'typename'): raise RuntimeError(f'Target type is not set for target class "{type(self).__name__}". This is a bug') + # dataclass comparators? def __lt__(self, other: object) -> bool: if not isinstance(other, Target): return NotImplemented @@ -1619,6 +1604,7 @@ class Generator(HoldableObject): def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], arguments: T.List[str], output: T.List[str], + # how2dataclass *, depfile: T.Optional[str] = None, capture: bool = False, @@ -1691,24 +1677,27 @@ class Generator(HoldableObject): return output +@dataclass(eq=False) class GeneratedList(HoldableObject): """The output of generator.process.""" - def __init__(self, generator: Generator, subdir: str, - preserve_path_from: T.Optional[str], - extra_args: T.List[str]): - self.generator = generator - self.name = generator.exe + generator: Generator + subdir: str + preserve_path_from: T.Optional[str] + extra_args: T.List[str] + + def __post_init__(self) -> None: + self.name = self.generator.exe self.depends: T.Set['CustomTarget'] = set() # Things this target depends on (because e.g. a custom target was used as input) - self.subdir = subdir self.infilelist: T.List['File'] = [] self.outfilelist: T.List[str] = [] self.outmap: T.Dict[File, T.List[str]] = {} self.extra_depends = [] # XXX: Doesn't seem to be used? self.depend_files: T.List[File] = [] - self.preserve_path_from = preserve_path_from - self.extra_args: T.List[str] = extra_args if extra_args is not None else [] + + if self.extra_args is None: + self.extra_args: T.List[str] = [] if isinstance(self.generator.exe, programs.ExternalProgram): if not self.generator.exe.found(): @@ -2712,6 +2701,7 @@ class Jar(BuildTarget): return ['-cp', os.pathsep.join(cp_paths)] return [] +@dataclass(eq=False) class CustomTargetIndex(HoldableObject): """A special opaque object returned by indexing a CustomTarget. This object @@ -2720,11 +2710,12 @@ class CustomTargetIndex(HoldableObject): the sources. """ - def __init__(self, target: CustomTarget, output: str): + target: CustomTarget + output: str + + def __post_init__(self) -> None: self.typename = 'custom' - self.target = target - self.output = output - self.for_machine = target.for_machine + self.for_machine = self.target.for_machine @property def name(self) -> str: @@ -2800,45 +2791,41 @@ class ConfigurationData(HoldableObject): # A bit poorly named, but this represents plain data files to copy # during install. +@dataclass(eq=False) class Data(HoldableObject): - def __init__(self, sources: T.List[File], install_dir: str, install_dir_name: str, - install_mode: 'FileMode', subproject: str, - rename: T.List[str] = None, - install_tag: T.Optional[str] = None, - data_type: str = None): - self.sources = sources - self.install_dir = install_dir - self.install_dir_name = install_dir_name - self.install_mode = install_mode - self.install_tag = install_tag - if rename is None: + sources: T.List[File] + install_dir: str + install_dir_name: str + install_mode: 'FileMode' + subproject: str + rename: T.List[str] = None + install_tag: T.Optional[str] = None + data_type: str = None + + def __post_init__(self) -> None: + if self.rename is None: self.rename = [os.path.basename(f.fname) for f in self.sources] - else: - self.rename = rename - self.subproject = subproject - self.data_type = data_type +@dataclass(eq=False) class SymlinkData(HoldableObject): - def __init__(self, target: str, name: str, install_dir: str, - subproject: str, install_tag: T.Optional[str] = None): - self.target = target - if name != os.path.basename(name): - raise InvalidArguments(f'Link name is "{name}", but link names cannot contain path separators. ' + target: str + name: str + install_dir: str + subproject: str + install_tag: T.Optional[str] = None + + def __post_init__(self) -> None: + if self.name != os.path.basename(self.name): + raise InvalidArguments(f'Link name is "{self.name}", but link names cannot contain path separators. ' 'The dir part should be in install_dir.') - self.name = name - self.install_dir = install_dir - self.subproject = subproject - self.install_tag = install_tag +@dataclass(eq=False) class TestSetup: - def __init__(self, exe_wrapper: T.List[str], gdb: bool, - timeout_multiplier: int, env: EnvironmentVariables, - exclude_suites: T.List[str]): - self.exe_wrapper = exe_wrapper - self.gdb = gdb - self.timeout_multiplier = timeout_multiplier - self.env = env - self.exclude_suites = exclude_suites + exe_wrapper: T.List[str] + gdb: bool + timeout_multiplier: int + env: EnvironmentVariables + exclude_suites: T.List[str] def get_sources_string_names(sources, backend): ''' diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 3a2923b0f..1b5f7288c 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from dataclasses import dataclass import subprocess import typing as T from enum import Enum @@ -234,27 +235,15 @@ 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) +@dataclass(unsafe_hash=True) class MachineInfo(HoldableObject): - def __init__(self, system: str, cpu_family: str, cpu: str, endian: str): - self.system = system - self.cpu_family = cpu_family - self.cpu = cpu - self.endian = endian - self.is_64_bit = cpu_family in CPU_FAMILIES_64_BIT # type: bool + system: str + cpu_family: str + cpu: str + endian: str - def __eq__(self, other: object) -> bool: - if not isinstance(other, MachineInfo): - return NotImplemented - return \ - self.system == other.system and \ - self.cpu_family == other.cpu_family and \ - self.cpu == other.cpu and \ - self.endian == other.endian - - def __ne__(self, other: object) -> bool: - if not isinstance(other, MachineInfo): - return NotImplemented - return not self.__eq__(other) + def __post_init__(self) -> None: + self.is_64_bit: bool = self.cpu_family in CPU_FAMILIES_64_BIT def __repr__(self) -> str: return f'' diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 0f493f2c8..c6fcaa018 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -19,6 +19,7 @@ from .exceptions import InterpreterException, InvalidArguments from .operator import MesonOperator from ._unholder import _unholder +from dataclasses import dataclass from functools import wraps import abc import itertools @@ -99,10 +100,9 @@ def disablerIfNotFound(f: TV_func) -> TV_func: return ret return T.cast(TV_func, wrapped) +@dataclass(repr=False, eq=False) class permittedKwargs: - - def __init__(self, permitted: T.Set[str]): - self.permitted = permitted # type: T.Set[str] + permitted: T.Set[str] def __call__(self, f: TV_func) -> TV_func: @wraps(f) @@ -575,6 +575,7 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]: return inner +# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374 class FeatureCheckBase(metaclass=abc.ABCMeta): "Base class for feature version checks" @@ -738,6 +739,7 @@ class FeatureDeprecated(FeatureCheckBase): mlog.warning(*args, location=self.location) +# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374 class FeatureCheckKwargsBase(metaclass=abc.ABCMeta): @property diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index c470e29e9..735766e00 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from dataclasses import dataclass import re import codecs import textwrap @@ -87,15 +88,15 @@ class BlockParseException(MesonException): TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool) +@dataclass(eq=False) class Token(T.Generic[TV_TokenTypes]): - def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: T.Tuple[int, int], value: TV_TokenTypes): - self.tid = tid # type: str - self.filename = filename # type: str - self.line_start = line_start # type: int - self.lineno = lineno # type: int - self.colno = colno # type: int - self.bytespan = bytespan # type: T.Tuple[int, int] - self.value = value # type: TV_TokenTypes + tid: str + filename: str + line_start: int + lineno: int + colno: int + bytespan: T.Tuple[int, int] + value: TV_TokenTypes def __eq__(self, other: object) -> bool: if isinstance(other, str): @@ -237,13 +238,19 @@ class Lexer: if not matched: raise ParseException('lexer', self.getline(line_start), lineno, col) +@dataclass(eq=False) class BaseNode: - def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None): - self.lineno = lineno # type: int - self.colno = colno # type: int - self.filename = filename # type: str - self.end_lineno = end_lineno if end_lineno is not None else self.lineno - self.end_colno = end_colno if end_colno is not None else self.colno + lineno: int + colno: int + filename: str + end_lineno: T.Optional[int] = None + end_colno: T.Optional[int] = None + + def __post_init__(self) -> None: + if self.end_lineno is None: + self.end_lineno = self.lineno + if self.end_colno is None: + self.end_colno = self.colno # Attributes for the visitors self.level = 0 # type: int diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 67bfca898..4eb3632be 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass, InitVar import os, subprocess import argparse import asyncio @@ -94,18 +95,22 @@ class Logger: self.print_progress() +@dataclass(eq=False) class Runner: - def __init__(self, logger: Logger, r: Resolver, wrap: PackageDefinition, repo_dir: str, options: 'Arguments') -> None: + logger: Logger + r: InitVar[Resolver] + wrap: PackageDefinition + repo_dir: str + options: 'Arguments' + + def __post_init__(self, r: Resolver) -> None: # FIXME: Do a copy because Resolver.resolve() is stateful method that # cannot be called from multiple threads. self.wrap_resolver = copy.copy(r) - self.wrap_resolver.dirname = os.path.join(r.subdir_root, wrap.directory) - self.wrap = self.wrap_resolver.wrap = wrap - self.repo_dir = repo_dir - self.options = options - self.run_method: T.Callable[[], bool] = options.subprojects_func.__get__(self) # type: ignore + self.wrap_resolver.dirname = os.path.join(r.subdir_root, self.wrap.directory) + self.wrap_resolver.wrap = self.wrap + self.run_method: T.Callable[[], bool] = self.options.subprojects_func.__get__(self) # type: ignore self.log_queue: T.List[T.Tuple[mlog.TV_LoggableList, T.Any]] = [] - self.logger = logger def log(self, *args: mlog.TV_Loggable, **kwargs: T.Any) -> None: self.log_queue.append((list(args), kwargs)) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index f2b59bded..80756af2d 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -14,6 +14,7 @@ from .. import mlog import contextlib +from dataclasses import dataclass import urllib.request import urllib.error import urllib.parse @@ -203,13 +204,15 @@ def verbose_git(cmd: T.List[str], workingdir: str, check: bool = False) -> bool: except mesonlib.GitException as e: raise WrapException(str(e)) +@dataclass(eq=False) class Resolver: - def __init__(self, source_dir: str, subdir: str, subproject: str = '', wrap_mode: WrapMode = WrapMode.default) -> None: - self.source_dir = source_dir - self.subdir = subdir - self.subproject = subproject - self.wrap_mode = wrap_mode - self.subdir_root = os.path.join(source_dir, subdir) + source_dir: str + subdir: str + subproject: str = '' + wrap_mode: WrapMode = WrapMode.default + + def __post_init__(self) -> None: + self.subdir_root = os.path.join(self.source_dir, self.subdir) self.cachedir = os.path.join(self.subdir_root, 'packagecache') self.wraps = {} # type: T.Dict[str, PackageDefinition] self.provided_deps = {} # type: T.Dict[str, PackageDefinition]