diff options
66 files changed, 1582 insertions, 1157 deletions
diff --git a/docs/markdown/Quick-guide.md b/docs/markdown/Quick-guide.md index 1363a69..c1de820 100644 --- a/docs/markdown/Quick-guide.md +++ b/docs/markdown/Quick-guide.md @@ -29,7 +29,7 @@ Requirements * [Ninja](https://github.com/ninja-build/ninja/) *Ninja is only needed if you use the Ninja backend. Meson can also -generate native VS and XCode project files.* +generate native VS and Xcode project files.* Installation using package manager diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index f3f87cc..de7f9f2 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -515,6 +515,8 @@ This function supports the following keyword arguments: in this case the subproject must use `meson.override_dependency('dependency_name', subproj_dep)` to specify the dependency object used in the superproject. + If the value is an empty list, it has the same effect as + `allow_fallback: false`. - `language` *(since 0.42.0)*: defines what language-specific dependency to find if it's available for multiple languages. - `method`: defines the way the dependency is detected, the default is @@ -2561,6 +2563,12 @@ module](#shared_module). this and will also allow Meson to setup inter-target dependencies correctly. Please file a bug if that doesn't work for you. +- `path()` *(since 0.59.0)* **(deprecated)**: does the exact same + as `full_path()`. **NOTE:** This function is solely kept for compatebility + with [`external program`](#external-program-object) objects. It will be + removed once the, also deprecated, corresponding `path()` function in the + `external program` object is removed. + - `private_dir_include()`: returns a opaque value that works like `include_directories` but points to the private directory of this target, usually only needed if an another target needs to access @@ -2568,6 +2576,11 @@ module](#shared_module). - `name()` *(since 0.54.0)*: returns the target name. +- `found()` *(since 0.59.0)*: Always returns `true`. This function is meant + to make executables objects feature compatible with + [`external program`](#external-program-object) objects. This simplifies + use-cases where an executable is used instead of an external program. + ### `configuration` data object diff --git a/docs/markdown/_include_qt_base.md b/docs/markdown/_include_qt_base.md index 4c50abc..aff9146 100644 --- a/docs/markdown/_include_qt_base.md +++ b/docs/markdown/_include_qt_base.md @@ -41,7 +41,7 @@ It takes no positional arguments, and the following keyword arguments: - `extra_args` string[]: Extra arguments to pass directly to `qt-moc` - `method` string: The method to use to detect qt, see `dependency()` for more information. - - `include_directories` IncludeDirectory[]: A list of `include_directory()` + - `include_directories` (string | IncludeDirectory)[]: A list of `include_directory()` objects used when transpiling the .moc files ## preprocess @@ -62,16 +62,16 @@ sources += qt.preprocess(qresources : ['resources']) ``` This method takes the following keyword arguments: - - `qresources`: a list of strings, Files, Custom Targets, or Build Targets to pass the `rcc` compiler - - `ui_files`: a list of strings, Files, Custom Targets, or Build Targets to pass the `uic` compiler - - `moc_sources`: a list of strings, Files, Custom Targets, or Build Targets to pass the `moc` compiler the - - `moc_headers`: a list of strings, Files, Custom Targets, or Build Targets to pass the `moc` compiler. These will be converted into .cpp files - - `include_directories`, the directories to add to header search path for `moc` (optional) - - `moc_extra_arguments`, any additional arguments to `moc` (optional). Available since v0.44.0. - - `uic_extra_arguments`, any additional arguments to `uic` (optional). Available since v0.49.0. - - `rcc_extra_arguments`, any additional arguments to `rcc` (optional). Available since v0.49.0. - - `dependencies`, dependency objects needed by moc. Available since v0.48.0. - - `sources`, a list of extra sources, which are added to the output unchaged. Deprecated in 0.59.0 + - `qresources` (string | File | CustomTarget | BuildTarget)[]: Passed to the RCC compiler + - `ui_files`: (string | File | CustomTarget | BuilduTarget)[]: Passed the `uic` compiler + - `moc_sources`: (string | File | CustomTarget | BuildTarget)[]: Passed the `moc` compiler the + - `moc_headers`: (string | File | CustomTarget | BuildTarget)[]: Passied the `moc` compiler. These will be converted into .cpp files + - `include_directories` (IncludeDirectories | string)[], the directories to add to header search path for `moc` + - `moc_extra_arguments` string[]: any additional arguments to `moc`. Since v0.44.0. + - `uic_extra_arguments` string[]: any additional arguments to `uic`. Since v0.49.0. + - `rcc_extra_arguments` string[]: any additional arguments to `rcc`. Since v0.49.0. + - `dependencies` Dependency[]: dependency objects needed by moc. Available since v0.48.0. + - `sources`: a list of extra sources, which are added to the output unchaged. Deprecated in 0.59.0. It returns an array of targets and sources to pass to a compilation target. diff --git a/docs/markdown/snippets/build-target-found.md b/docs/markdown/snippets/build-target-found.md new file mode 100644 index 0000000..60c5083 --- /dev/null +++ b/docs/markdown/snippets/build-target-found.md @@ -0,0 +1,16 @@ +## New `build target` methods + +The [`build target` object](Reference-manual.md#build-target-object) now supports +the following two functions, to ensure feature compatebility with +[`external program` objects](Reference-manual.html#external-program-object): + +- `found()`: Always returns `true`. This function is meant + to make executables objects feature compatible with + `external program` objects. This simplifies + use-cases where an executable is used instead of an external program. + +- `path()`: **(deprecated)** does the exact same as `full_path()`. + **NOTE:** This function is solely kept for compatebility + with `external program` objects. It will be + removed once the, also deprecated, corresponding `path()` function in the + `external program` object is removed. diff --git a/docs/markdown/snippets/compiler_argument_checking.md b/docs/markdown/snippets/compiler_argument_checking.md new file mode 100644 index 0000000..0e038ec --- /dev/null +++ b/docs/markdown/snippets/compiler_argument_checking.md @@ -0,0 +1,6 @@ +## Compiler argument checking for `get_supported_arguments` + +The compiler method `get_supported_arguments` now supports +a new keyword argument named `checked` that can be set to +one of `warn`, `require` or `off` (defaults to `off`) to +enforce argument checks. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 71c7f47..b9dfc7b 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -16,10 +16,20 @@ # or an interpreter-based tool. from .visitor import AstVisitor -from .. import interpreterbase, mparser, mesonlib +from .. import mparser, mesonlib from .. import environment -from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest, TYPE_nvar, TYPE_nkwargs +from ..interpreterbase import ( + MesonInterpreterObject, + InterpreterBase, + InvalidArguments, + BreakRequest, + ContinueRequest, + default_resolve_key, + TYPE_nvar, + TYPE_nkwargs, +) + from ..mparser import ( AndNode, ArgumentNode, @@ -45,28 +55,31 @@ from ..mparser import ( import os, sys import typing as T -class DontCareObject(interpreterbase.InterpreterObject): +class DontCareObject(MesonInterpreterObject): pass -class MockExecutable(interpreterbase.InterpreterObject): +class MockExecutable(MesonInterpreterObject): pass -class MockStaticLibrary(interpreterbase.InterpreterObject): +class MockStaticLibrary(MesonInterpreterObject): pass -class MockSharedLibrary(interpreterbase.InterpreterObject): +class MockSharedLibrary(MesonInterpreterObject): pass -class MockCustomTarget(interpreterbase.InterpreterObject): +class MockCustomTarget(MesonInterpreterObject): pass -class MockRunTarget(interpreterbase.InterpreterObject): +class MockRunTarget(MesonInterpreterObject): pass ADD_SOURCE = 0 REMOVE_SOURCE = 1 -class AstInterpreter(interpreterbase.InterpreterBase): +_T = T.TypeVar('_T') +_V = T.TypeVar('_V') + +class AstInterpreter(InterpreterBase): def __init__(self, source_root: str, subdir: str, subproject: str, visitors: T.Optional[T.List[AstVisitor]] = None): super().__init__(source_root, subdir, subproject) self.visitors = visitors if visitors is not None else [] @@ -131,6 +144,12 @@ class AstInterpreter(interpreterbase.InterpreterBase): 'range': self.func_do_nothing, }) + def _unholder_args(self, args: _T, kwargs: _V) -> T.Tuple[_T, _V]: + return args, kwargs + + def _holderify(self, res: _T) -> _T: + return res + def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> bool: return True @@ -224,7 +243,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): def reduce_arguments( self, args: mparser.ArgumentNode, - key_resolver: T.Callable[[mparser.BaseNode], str] = interpreterbase.default_resolve_key, + key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, duplicate_key_error: T.Optional[str] = None, ) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]: if isinstance(args, ArgumentNode): diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 64e0b18..2652ae6 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -32,7 +32,7 @@ from .. import mlog from ..compilers import LANGUAGES_USING_LDFLAGS from ..mesonlib import ( File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy, - classify_unity_sources, unholder, OptionKey, join_args + classify_unity_sources, OptionKey, join_args ) if T.TYPE_CHECKING: @@ -237,7 +237,7 @@ class Backend: def generate(self) -> None: raise RuntimeError(f'generate is not implemented in {type(self).__name__}') - def get_target_filename(self, t, *, warn_multi_output: bool = True): + def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True): if isinstance(t, build.CustomTarget): if warn_multi_output and len(t.get_outputs()) != 1: mlog.warning(f'custom_target {t.name!r} has more than one output! ' @@ -250,7 +250,7 @@ class Backend: filename = t.get_filename() return os.path.join(self.get_target_dir(t), filename) - def get_target_filename_abs(self, target): + def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) def get_base_options_for_target(self, target: build.BuildTarget) -> OptionOverrideProxy: @@ -309,7 +309,7 @@ class Backend: raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable') @lru_cache(maxsize=None) - def get_target_dir(self, target): + def get_target_dir(self, target: build.Target) -> str: if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': dirname = target.get_subdir() else: @@ -329,7 +329,7 @@ class Backend: return os.path.join(self.build_to_src, target_dir) return self.build_to_src - def get_target_private_dir(self, target): + def get_target_private_dir(self, target: build.Target) -> str: return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') def get_target_private_dir_abs(self, target): @@ -963,7 +963,7 @@ class Backend: depends = set(t.depends) if isinstance(exe, build.Target): depends.add(exe) - for a in unholder(t.cmd_args): + for a in t.cmd_args: if isinstance(a, build.Target): depends.add(a) if isinstance(a, build.BuildTarget): @@ -1091,10 +1091,10 @@ class Backend: # also be built by default. XXX: Sometime in the future these should be # built only before running tests. for t in self.build.get_tests(): - exe = unholder(t.exe) + exe = t.exe if isinstance(exe, (build.CustomTarget, build.BuildTarget)): result[exe.get_id()] = exe - for arg in unholder(t.cmd_args): + for arg in t.cmd_args: if not isinstance(arg, (build.CustomTarget, build.BuildTarget)): continue result[arg.get_id()] = arg @@ -1133,7 +1133,7 @@ class Backend: Returns the path to them relative to the build root directory. ''' srcs = [] - for i in unholder(target.get_sources()): + for i in target.get_sources(): if isinstance(i, str): fname = [os.path.join(self.build_to_src, target.subdir, i)] elif isinstance(i, build.BuildTarget): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 688149b..192cef3 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -40,7 +40,7 @@ from ..compilers import ( from ..linkers import ArLinker, RSPFileSyntax from ..mesonlib import ( File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine, - ProgressBar, quote_arg, unholder, + ProgressBar, quote_arg ) from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey from .backends import CleanTrees @@ -937,7 +937,7 @@ int dummy; self.generate_target(t) def custom_target_generator_inputs(self, target): - for s in unholder(target.sources): + for s in target.sources: if isinstance(s, build.GeneratedList): self.generate_genlist_for_target(s, target) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 0a792fc..03f97b2 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -20,6 +20,7 @@ import itertools, pathlib import os import pickle import re +import textwrap import typing as T from . import environment @@ -27,10 +28,12 @@ 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, + get_filenames_templates_dict, substitute_values, has_path_sep, OptionKey, PerMachineDefaultable, + MesonBugException, FileOrString, ) from .compilers import ( Compiler, is_object, clink_langs, sort_clink, lang_suffixes, @@ -45,7 +48,6 @@ if T.TYPE_CHECKING: from .mesonlib import FileMode, FileOrString from .modules import ModuleState from .backend.backends import Backend - from .interpreter.interpreterobjects import GeneratorHolder pch_kwargs = {'c_pch', 'cpp_pch'} @@ -124,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'], @@ -160,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, @@ -181,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'], @@ -326,29 +328,30 @@ class Build: return link_args.get(compiler.get_language(), []) -class IncludeDirs: - def __init__(self, curdir, dirs, is_system, extra_build_dirs=None): +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): self.curdir = curdir self.incdirs = dirs self.is_system = is_system + # Interpreter has validated that all given directories # actually exist. - if extra_build_dirs is None: - self.extra_build_dirs = [] - else: - self.extra_build_dirs = extra_build_dirs + self.extra_build_dirs: T.List[str] = extra_build_dirs or [] - def __repr__(self): + def __repr__(self) -> str: r = '<{} {}/{}>' return r.format(self.__class__.__name__, self.curdir, self.incdirs) - def get_curdir(self): + def get_curdir(self) -> str: return self.curdir - def get_incdirs(self): + def get_incdirs(self) -> T.List[str]: return self.incdirs - def get_extra_build_dirs(self): + def get_extra_build_dirs(self) -> T.List[str]: return self.extra_build_dirs def to_string_list(self, sourcedir: str) -> T.List[str]: @@ -358,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 ''' @@ -415,7 +418,7 @@ class ExtractedObjects: for source in self.get_sources(self.srclist, self.genlist) ] -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() @@ -457,16 +460,18 @@ class EnvironmentVariables: env[name] = method(env, name, values, separator) return env -class Target: +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): # Fix failing test 53 when this becomes an error. - mlog.warning(f'''Target "{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.''') + mlog.warning(textwrap.dedent(f'''\ + Target "{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 @@ -677,7 +682,7 @@ class BuildTarget(Target): def process_objectlist(self, objects): assert(isinstance(objects, list)) - for s in unholder(objects): + for s in objects: if isinstance(s, (str, File, ExtractedObjects)): self.objects.append(s) elif isinstance(s, (GeneratedList, CustomTarget)): @@ -773,7 +778,7 @@ class BuildTarget(Target): # which is what we need. if not is_object(s): sources.append(s) - for d in unholder(self.external_deps): + for d in self.external_deps: for s in d.sources: if isinstance(s, (str, File)): sources.append(s) @@ -844,7 +849,7 @@ class BuildTarget(Target): link_depends. """ sources = listify(sources) - for s in unholder(sources): + for s in sources: if isinstance(s, File): self.link_depends.append(s) elif isinstance(s, str): @@ -861,35 +866,16 @@ class BuildTarget(Target): def get_original_kwargs(self): return self.kwargs - def unpack_holder(self, d): - d = listify(d) - newd = [] - for i in d: - if isinstance(i, list): - i = self.unpack_holder(i) - elif hasattr(i, 'held_object'): - i = i.held_object - for t in ['dependencies', 'link_with', 'include_directories', 'sources']: - if hasattr(i, t): - setattr(i, t, self.unpack_holder(getattr(i, t))) - newd.append(i) - return newd - def copy_kwargs(self, kwargs): self.kwargs = copy.copy(kwargs) - # This sucks quite badly. Arguments - # are holders but they can't be pickled - # so unpack those known. for k, v in self.kwargs.items(): if isinstance(v, list): - self.kwargs[k] = self.unpack_holder(v) - if hasattr(v, 'held_object'): - self.kwargs[k] = v.held_object + self.kwargs[k] = listify(v, flatten=True) for t in ['dependencies', 'link_with', 'include_directories', 'sources']: if t in self.kwargs: - self.kwargs[t] = self.unpack_holder(self.kwargs[t]) + self.kwargs[t] = listify(self.kwargs[t], flatten=True) - def extract_objects(self, srclist): + def extract_objects(self, srclist: T.List[FileOrString]) -> ExtractedObjects: obj_src = [] sources_set = set(self.sources) for src in srclist: @@ -898,14 +884,14 @@ class BuildTarget(Target): elif isinstance(src, File): FeatureNew.single_use('File argument for extract_objects', '0.50.0', self.subproject) else: - raise MesonException('Object extraction arguments must be strings or Files.') + raise MesonException(f'Object extraction arguments must be strings or Files (got {type(src).__name__}).') # FIXME: It could be a generated source if src not in sources_set: raise MesonException(f'Tried to extract unknown source {src}.') obj_src.append(src) return ExtractedObjects(self, obj_src) - def extract_all_objects(self, recursive=True): + def extract_all_objects(self, recursive: bool = True) -> ExtractedObjects: return ExtractedObjects(self, self.sources, self.generated, self.objects, recursive) @@ -957,13 +943,15 @@ class BuildTarget(Target): kwargs.get('modules', []) self.need_install = kwargs.get('install', self.need_install) llist = extract_as_list(kwargs, 'link_with') - for linktarget in unholder(llist): + for linktarget in llist: if isinstance(linktarget, dependencies.ExternalLibrary): - raise MesonException('''An external library was used in link_with keyword argument, which -is reserved for libraries built as part of this project. External -libraries must be passed using the dependencies keyword argument -instead, because they are conceptually "external dependencies", -just like those detected with the dependency() function.''') + raise MesonException(textwrap.dedent('''\ + An external library was used in link_with keyword argument, which + is reserved for libraries built as part of this project. External + libraries must be passed using the dependencies keyword argument + instead, because they are conceptually "external dependencies", + just like those detected with the dependency() function.\ + ''')) self.link(linktarget) lwhole = extract_as_list(kwargs, 'link_whole') for linktarget in lwhole: @@ -998,7 +986,7 @@ just like those detected with the dependency() function.''') if dfeature_debug: dfeatures['debug'] = dfeature_debug if 'd_import_dirs' in kwargs: - dfeature_import_dirs = unholder(extract_as_list(kwargs, 'd_import_dirs')) + dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs') for d in dfeature_import_dirs: if not isinstance(d, IncludeDirs): raise InvalidArguments('Arguments to d_import_dirs must be include_directories.') @@ -1012,8 +1000,11 @@ just like those detected with the dependency() function.''') raise InvalidArguments('Link_args arguments must be strings.') for l in self.link_args: if '-Wl,-rpath' in l or l.startswith('-rpath'): - mlog.warning('''Please do not define rpath with a linker argument, use install_rpath or build_rpath properties instead. -This will become a hard error in a future Meson release.''') + mlog.warning(textwrap.dedent('''\ + Please do not define rpath with a linker argument, use install_rpath + or build_rpath properties instead. + This will become a hard error in a future Meson release.\ + ''')) self.process_link_depends(kwargs.get('link_depends', []), environment) # Target-specific include dirs must be added BEFORE include dirs from # internal deps (added inside self.add_deps()) to override them. @@ -1192,7 +1183,7 @@ This will become a hard error in a future Meson release.''') def add_deps(self, deps): deps = listify(deps) - for dep in unholder(deps): + for dep in deps: if dep in self.added_deps: continue if isinstance(dep, dependencies.InternalDependency): @@ -1242,7 +1233,9 @@ You probably should put it in link_with instead.''') return isinstance(self, StaticLibrary) and not self.need_install def link(self, target): - for t in unholder(listify(target)): + for t in listify(target): + if isinstance(t, BothLibraries): + t = t.get_preferred_library() if isinstance(self, StaticLibrary) and self.need_install: if isinstance(t, (CustomTarget, CustomTargetIndex)): if not t.should_install(): @@ -1270,7 +1263,10 @@ You probably should put it in link_with instead.''') self.link_targets.append(t) def link_whole(self, target): - for t in unholder(listify(target)): + for t in listify(target): + # Always use the static library from BothLibraries, since shared libs aren't supported anyway + if isinstance(t, BothLibraries): + t = t.static if isinstance(t, (CustomTarget, CustomTargetIndex)): if not t.is_linkable_target(): raise InvalidArguments(f'Custom target {t!r} is not linkable.') @@ -1336,7 +1332,7 @@ You probably should put it in link_with instead.''') def add_include_dirs(self, args, set_is_system: T.Optional[str] = None): ids = [] - for a in unholder(args): + for a in args: if not isinstance(a, IncludeDirs): raise InvalidArguments('Include directory to be added is not an include directory object.') ids.append(a) @@ -1500,7 +1496,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], @@ -1576,7 +1572,7 @@ class Generator: return output -class GeneratedList: +class GeneratedList(HoldableObject): """The output of generator.process.""" @@ -1832,6 +1828,8 @@ class SharedLibrary(BuildTarget): self.gcc_import_filename = None # The debugging information file this target will generate self.debug_filename = None + # Use by the pkgconfig module + self.shared_library_only = False super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) if 'rust' in self.compilers: # If no crate type is specified, or it's the generic lib type, use dylib @@ -2048,7 +2046,7 @@ class SharedLibrary(BuildTarget): # Visual Studio module-definitions file if 'vs_module_defs' in kwargs: - path = unholder(kwargs['vs_module_defs']) + path = kwargs['vs_module_defs'] if isinstance(path, str): if os.path.isabs(path): self.vs_module_defs = File.from_absolute_file(path) @@ -2151,9 +2149,22 @@ class SharedModule(SharedLibrary): def get_default_install_dir(self, environment): return environment.get_shared_module_dir() +class BothLibraries(HoldableObject): + def __init__(self, shared: SharedLibrary, static: StaticLibrary) -> None: + self._preferred_library = 'shared' + self.shared = shared + self.static = static + + def get_preferred_library(self) -> BuildTarget: + if self._preferred_library == 'shared': + return self.shared + elif self._preferred_library == 'static': + return self.static + raise MesonBugException(f'self._preferred_library == "{self._preferred_library}" is neither "shared" nor "static".') + class CommandBase: def flatten_command(self, cmd): - cmd = unholder(listify(cmd)) + cmd = listify(cmd) final_cmd = [] for c in cmd: if isinstance(c, str): @@ -2228,7 +2239,7 @@ class CustomTarget(Target, CommandBase): def get_target_dependencies(self): deps = self.dependencies[:] deps += self.extra_depends - for c in unholder(self.sources): + for c in self.sources: if isinstance(c, (BuildTarget, CustomTarget)): deps.append(c) return deps @@ -2253,7 +2264,7 @@ class CustomTarget(Target, CommandBase): def process_kwargs(self, kwargs, backend): self.process_kwargs_base(kwargs) - self.sources = unholder(extract_as_list(kwargs, 'input')) + self.sources = extract_as_list(kwargs, 'input') if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') self.outputs = listify(kwargs['output']) @@ -2332,7 +2343,7 @@ class CustomTarget(Target, CommandBase): if not isinstance(self.build_always_stale, bool): raise InvalidArguments('Argument build_always_stale must be a boolean.') extra_deps, depend_files = [extract_as_list(kwargs, c, pop=False) for c in ['depends', 'depend_files']] - for ed in unholder(extra_deps): + for ed in extra_deps: if not isinstance(ed, (CustomTarget, BuildTarget)): raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target ' f'(executable or a library) got: {type(ed)}({ed})') @@ -2368,7 +2379,7 @@ class CustomTarget(Target, CommandBase): def get_generated_lists(self): genlists = [] - for c in unholder(self.sources): + for c in self.sources: if isinstance(c, GeneratedList): genlists.append(c) return genlists @@ -2419,7 +2430,7 @@ class CustomTarget(Target, CommandBase): def type_suffix(self): return "@cus" - def __getitem__(self, index): + def __getitem__(self, index: int) -> 'CustomTargetIndex': return CustomTargetIndex(self, self.outputs[index]) def __setitem__(self, index, value): @@ -2519,7 +2530,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 @@ -2575,10 +2586,16 @@ 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]] + self.values: T.Dict[ + str, + T.Tuple[ + T.Union[str, int, bool], + T.Optional[str] + ] + ] = {} def __repr__(self): return repr(self.values) @@ -2594,7 +2611,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): @@ -2623,7 +2640,7 @@ def get_sources_string_names(sources, backend): get all the output basenames. ''' names = [] - for s in unholder(sources): + for s in sources: if isinstance(s, str): names.append(s) elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)): diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index f79f7d2..745def1 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -24,7 +24,7 @@ from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .. import mlog, mesonlib from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey from ..mesondata import mesondata -from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header +from ..compilers.compilers import assembler_suffixes, lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from ..programs import ExternalProgram from enum import Enum from functools import lru_cache @@ -435,7 +435,7 @@ class ConverterTarget: self.link_libraries = temp # Filter out files that are not supported by the language - supported = list(header_suffixes) + list(obj_suffixes) + supported = list(assembler_suffixes) + list(header_suffixes) + list(obj_suffixes) for i in self.languages: supported += list(lang_suffixes[i]) supported = [f'.{x}' for x in supported] diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index b46c8f6..be4809f 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -15,6 +15,7 @@ # Public symbols for compilers sub-package when using 'from . import compilers' __all__ = [ 'Compiler', + 'RunResult', 'all_languages', 'base_options', @@ -112,6 +113,7 @@ __all__ = [ # Bring symbols from each module into compilers sub-package namespace from .compilers import ( Compiler, + RunResult, all_languages, base_options, clib_langs, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 19288eb..ff87819 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, ) @@ -72,6 +73,8 @@ c_suffixes = lang_suffixes['c'] + ('h',) # type: T.Tuple[str, ...] # List of languages that by default consume and output libraries following the # C ABI; these can generally be used interchangeably clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran',) # type: T.Tuple[str, ...] +# List of assembler suffixes that can be linked with C code directly by the linker +assembler_suffixes: T.Tuple[str, ...] = ('s', 'S') # List of languages that can be linked with C code directly by the linker # used in build.py:process_compilers() and build.py:get_dynamic_linker() clink_langs = ('d', 'cuda') + clib_langs # type: T.Tuple[str, ...] @@ -435,7 +438,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 +447,7 @@ class RunResult: self.stderr = stderr -class CompileResult: +class CompileResult(HoldableObject): """The result of Compiler.compiles (and friends).""" @@ -467,7 +470,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 89cec46..107e4b8 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 515edcf..e12c697 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 a93905a..307aac3 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 58ee729..62b09bf 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/compiler.py b/mesonbuild/interpreter/compiler.py index 3a3ce34..b1eef2f 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -1,22 +1,26 @@ import functools -from .interpreterobjects import (IncludeDirsHolder, ExternalLibraryHolder, - extract_required_kwarg, extract_search_dirs) +from ..interpreterbase.decorators import typed_kwargs, KwargInfo + +from .interpreterobjects import (extract_required_kwarg, extract_search_dirs) from .. import mesonlib from .. import mlog from .. import dependencies -from ..interpreterbase import (InterpreterObject, noPosargs, noKwargs, permittedKwargs, +from ..interpreterbase import (ObjectHolder, noPosargs, noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, disablerIfNotFound, - check_stringlist, InterpreterException, InvalidArguments, - InvalidCode) + check_stringlist, InterpreterException, InvalidArguments) import typing as T +import os + +if T.TYPE_CHECKING: + from ..interpreter import Interpreter + from ..compilers import Compiler, RunResult -class TryRunResultHolder(InterpreterObject): - def __init__(self, res): - super().__init__() - self.res = res +class TryRunResultHolder(ObjectHolder['RunResult']): + def __init__(self, res: 'RunResult', interpreter: 'Interpreter'): + super().__init__(res, interpreter) self.methods.update({'returncode': self.returncode_method, 'compiled': self.compiled_method, 'stdout': self.stdout_method, @@ -26,22 +30,22 @@ class TryRunResultHolder(InterpreterObject): @noPosargs @permittedKwargs({}) def returncode_method(self, args, kwargs): - return self.res.returncode + return self.held_object.returncode @noPosargs @permittedKwargs({}) def compiled_method(self, args, kwargs): - return self.res.compiled + return self.held_object.compiled @noPosargs @permittedKwargs({}) def stdout_method(self, args, kwargs): - return self.res.stdout + return self.held_object.stdout @noPosargs @permittedKwargs({}) def stderr_method(self, args, kwargs): - return self.res.stderr + return self.held_object.stderr header_permitted_kwargs = { 'required', @@ -61,12 +65,10 @@ find_library_permitted_kwargs = { find_library_permitted_kwargs |= {'header_' + k for k in header_permitted_kwargs} -class CompilerHolder(InterpreterObject): - def __init__(self, compiler: 'Compiler', env: 'Environment', subproject: str): - InterpreterObject.__init__(self) - self.compiler = compiler - self.environment = env - self.subproject = subproject +class CompilerHolder(ObjectHolder['Compiler']): + def __init__(self, compiler: 'Compiler', interpreter: 'Interpreter'): + super().__init__(compiler, interpreter) + self.environment = self.env self.methods.update({'compiles': self.compiles_method, 'links': self.links_method, 'get_id': self.get_id_method, @@ -101,6 +103,10 @@ class CompilerHolder(InterpreterObject): 'get_argument_syntax': self.get_argument_syntax_method, }) + @property + def compiler(self) -> 'Compiler': + return self.held_object + def _dep_msg(self, deps, endl): msg_single = 'with dependency {}' msg_many = 'with dependencies {}' @@ -139,9 +145,10 @@ class CompilerHolder(InterpreterObject): args = [] incdirs = mesonlib.extract_as_list(kwargs, 'include_directories') for i in incdirs: - if not isinstance(i, IncludeDirsHolder): + from ..build import IncludeDirs + if not isinstance(i, IncludeDirs): raise InterpreterException('Include directories argument must be an include_directories object.') - for idir in i.held_object.to_string_list(self.environment.get_source_dir()): + for idir in i.to_string_list(self.environment.get_source_dir()): args += self.compiler.get_include_args(idir, False) if not nobuiltins: opts = self.environment.coredata.options @@ -157,7 +164,7 @@ class CompilerHolder(InterpreterObject): final_deps = [] while deps: next_deps = [] - for d in mesonlib.unholder(mesonlib.listify(deps)): + for d in mesonlib.listify(deps): if not isinstance(d, dependencies.Dependency) or d.is_built(): raise InterpreterException('Dependencies must be external dependencies') final_deps.append(d) @@ -218,7 +225,7 @@ class CompilerHolder(InterpreterObject): else: h = mlog.red('NO (%d)' % result.returncode) mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h) - return TryRunResultHolder(result) + return result @noPosargs @permittedKwargs({}) @@ -609,7 +616,7 @@ class CompilerHolder(InterpreterObject): self.environment, self.compiler.language, silent=True) - return ExternalLibraryHolder(lib, self.subproject) + return lib @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static']) @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers']) @@ -654,7 +661,7 @@ class CompilerHolder(InterpreterObject): libtype, libname)) lib = dependencies.ExternalLibrary(libname, linkargs, self.environment, self.compiler.language) - return ExternalLibraryHolder(lib, self.subproject) + return lib @permittedKwargs({}) def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool: @@ -679,12 +686,24 @@ class CompilerHolder(InterpreterObject): return result @FeatureNew('compiler.get_supported_arguments', '0.43.0') - @permittedKwargs({}) - def get_supported_arguments_method(self, args, kwargs): + @typed_kwargs( + 'compiler.get_supported_arguments', + KwargInfo('checked', str, default='off', since='0.59.0', + validator=lambda s: 'must be one of "warn", "require" or "off"' if s not in ['warn', 'require', 'off'] else None) + ) + def get_supported_arguments_method(self, args: T.Sequence[str], kwargs: T.Dict[str, T.Any]): args = mesonlib.stringlistify(args) supported_args = [] + checked = kwargs.pop('checked') + for arg in args: - if self.has_argument_method(arg, kwargs): + if not self.has_argument_method(arg, kwargs): + msg = f'Compiler for {self.compiler.get_display_language()} does not support "{arg}"' + if checked == 'warn': + mlog.warning(msg) + elif checked == 'require': + raise mesonlib.MesonException(msg) + else: supported_args.append(arg) return supported_args diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 6edb129..019073c 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -1,12 +1,12 @@ -from .interpreterobjects import DependencyHolder, SubprojectHolder, extract_required_kwarg +from .interpreterobjects import SubprojectHolder, extract_required_kwarg from .. import mlog from .. import dependencies from .. import build from ..wrap import WrapMode from ..mesonlib import OptionKey, extract_as_list, stringlistify, version_compare_many -from ..dependencies import DependencyException, NotFoundDependency -from ..interpreterbase import (InterpreterObject, FeatureNew, +from ..dependencies import Dependency, DependencyException, NotFoundDependency +from ..interpreterbase import (MesonInterpreterObject, FeatureNew, InterpreterException, InvalidArguments, TYPE_nkwargs, TYPE_nvar) @@ -15,7 +15,7 @@ if T.TYPE_CHECKING: from .interpreter import Interpreter -class DependencyFallbacksHolder(InterpreterObject): +class DependencyFallbacksHolder(MesonInterpreterObject): def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None) -> None: super().__init__() self.interpreter = interpreter @@ -47,6 +47,10 @@ class DependencyFallbacksHolder(InterpreterObject): location=self.interpreter.current_node) return fbinfo = stringlistify(fbinfo) + if len(fbinfo) == 0: + # dependency('foo', fallback: []) is the same as dependency('foo', allow_fallback: false) + self.allow_fallback = False + return if len(fbinfo) == 1: FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject) subp_name, varname = fbinfo[0], None @@ -71,14 +75,14 @@ class DependencyFallbacksHolder(InterpreterObject): self.subproject_varname = varname self.subproject_kwargs = kwargs - def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: name = func_args[0] cached_dep = self._get_cached_dep(name, kwargs) if cached_dep: self._verify_fallback_consistency(cached_dep) return cached_dep - def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: # Note that there is no df.dependency() method, this is called for names # given as positional arguments to dependency_fallbacks(name1, ...). # We use kwargs from the dependency() function, for things like version, @@ -90,17 +94,17 @@ class DependencyFallbacksHolder(InterpreterObject): for_machine = self.interpreter.machine_from_native_kwarg(kwargs) identifier = dependencies.get_dep_identifier(name, kwargs) self.coredata.deps[for_machine].put(identifier, dep) - return DependencyHolder(dep, self.subproject) + return dep return None - def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: subp_name = func_args[0] varname = self.subproject_varname if subp_name and self._get_subproject(subp_name): return self._get_subproject_dep(subp_name, varname, kwargs) return None - def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: if self.nofallback: mlog.log('Not looking for a fallback subproject for the dependency', mlog.bold(self.display_name), 'because:\nUse of fallback dependencies is disabled.') @@ -124,7 +128,7 @@ class DependencyFallbacksHolder(InterpreterObject): return sub return None - def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: # Verify the subproject is found subproject = self._get_subproject(subp_name) if not subproject: @@ -166,7 +170,7 @@ class DependencyFallbacksHolder(InterpreterObject): return var_dep wanted = stringlistify(kwargs.get('version', [])) - found = var_dep.held_object.get_version() + found = var_dep.get_version() if not self._check_version(wanted, found): mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject', mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), @@ -179,7 +183,7 @@ class DependencyFallbacksHolder(InterpreterObject): mlog.normal_cyan(found) if found else None) return var_dep - def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]: + def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: # Unlike other methods, this one returns not-found dependency instead # of None in the case the dependency is cached as not-found, or if cached # version does not match. In that case we don't want to continue with @@ -198,7 +202,7 @@ class DependencyFallbacksHolder(InterpreterObject): if not cached_dep.found(): mlog.log('Dependency', mlog.bold(self.display_name), 'found:', mlog.red('NO'), *info) - return DependencyHolder(cached_dep, self.subproject) + return cached_dep else: info = [mlog.blue('(cached)')] cached_dep = self.coredata.deps[for_machine].get(identifier) @@ -216,24 +220,27 @@ class DependencyFallbacksHolder(InterpreterObject): info = [mlog.normal_cyan(found_vers), *info] mlog.log('Dependency', mlog.bold(self.display_name), 'found:', mlog.green('YES'), *info) - return DependencyHolder(cached_dep, self.subproject) + return cached_dep return None - def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[DependencyHolder]: - var_dep = subproject.held_object.variables.get(varname) - if not isinstance(var_dep, DependencyHolder): + def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[Dependency]: + try: + var_dep = subproject.get_variable_method([varname], {}) + except InvalidArguments: + var_dep = None + if not isinstance(var_dep, Dependency): mlog.warning(f'Variable {varname!r} in the subproject {subproject.subdir!r} is', 'not found' if var_dep is None else 'not a dependency object') return None return var_dep - def _verify_fallback_consistency(self, cached_dep: DependencyHolder): + def _verify_fallback_consistency(self, cached_dep: Dependency): subp_name = self.subproject_name varname = self.subproject_varname subproject = self._get_subproject(subp_name) if subproject and varname: var_dep = self._get_subproject_variable(subproject, varname) - if var_dep and cached_dep.found() and var_dep.held_object != cached_dep.held_object: + if var_dep and cached_dep.found() and var_dep != cached_dep: mlog.warning(f'Inconsistency: Subproject has overridden the dependency with another variable than {varname!r}') def _handle_featurenew_dependencies(self, name: str) -> None: @@ -249,8 +256,8 @@ class DependencyFallbacksHolder(InterpreterObject): elif name == 'openmp': FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject) - def _notfound_dependency(self) -> DependencyHolder: - return DependencyHolder(NotFoundDependency(self.environment), self.subproject) + def _notfound_dependency(self) -> NotFoundDependency: + return NotFoundDependency(self.environment) @staticmethod def _check_version(wanted: T.Optional[str], found: str) -> bool: @@ -260,7 +267,7 @@ class DependencyFallbacksHolder(InterpreterObject): return False return True - def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[DependencyHolder]], TYPE_nvar, TYPE_nkwargs]]: + def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], TYPE_nvar, TYPE_nkwargs]]: candidates = [] # 1. check if any of the names is cached already. for name in self.names: @@ -277,7 +284,7 @@ class DependencyFallbacksHolder(InterpreterObject): candidates.append((self._do_subproject, [self.subproject_name], self.subproject_kwargs)) return candidates - def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> DependencyHolder: + def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency: self.display_name = self.names[0] if self.names else '(anonymous)' mods = extract_as_list(kwargs, 'modules') if mods: @@ -325,7 +332,7 @@ class DependencyFallbacksHolder(InterpreterObject): func_kwargs['required'] = required and (i == last) kwargs['required'] = required and (i == last) dep = func(kwargs, func_args, func_kwargs) - if dep and dep.held_object.found(): + if dep and dep.found(): # Override this dependency to have consistent results in subsequent # dependency lookups. for name in self.names: @@ -333,7 +340,7 @@ class DependencyFallbacksHolder(InterpreterObject): identifier = dependencies.get_dep_identifier(name, kwargs) if identifier not in self.build.dependency_overrides[for_machine]: self.build.dependency_overrides[for_machine][identifier] = \ - build.DependencyOverride(dep.held_object, self.interpreter.current_node, explicit=False) + build.DependencyOverride(dep, self.interpreter.current_node, explicit=False) return dep elif required and (dep or i == last): # This was the last candidate or the dependency has been cached diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index b5335e6..5be99b4 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -21,33 +21,35 @@ from .. import optinterpreter from .. import compilers from ..wrap import wrap, WrapMode from .. import mesonlib -from ..mesonlib import FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep, unholder +from ..mesonlib import HoldableObject, FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep from ..programs import ExternalProgram, NonExistingExternalProgram from ..dependencies import Dependency from ..depfile import DepFile from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args -from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening +from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening, unholder_return from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from ..interpreterbase import InterpreterObject, Disabler, disablerIfNotFound from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs from ..interpreterbase import ObjectHolder, RangeHolder +from ..interpreterbase import TYPE_nkwargs, TYPE_nvar, TYPE_var from ..modules import ModuleObject, MutableModuleObject from ..cmake import CMakeInterpreter from ..backend.backends import Backend, ExecutableSerialisation +from . import interpreterobjects as OBJ +from . import compiler as compilerOBJ from .mesonmain import MesonMain -from .compiler import CompilerHolder -from .interpreterobjects import (SubprojectHolder, MachineHolder, EnvironmentVariablesHolder, - FeatureOptionHolder, ExternalProgramHolder, CustomTargetHolder, - RunTargetHolder, IncludeDirsHolder, ConfigurationDataHolder, - DependencyHolder, ModuleObjectHolder, GeneratedListHolder, - TargetHolder, CustomTargetIndexHolder, GeneratedObjectsHolder, - StaticLibraryHolder, ExecutableHolder, SharedLibraryHolder, - SharedModuleHolder, HeadersHolder, BothLibrariesHolder, - BuildTargetHolder, DataHolder, JarHolder, Test, RunProcess, - ManHolder, GeneratorHolder, InstallDirHolder, extract_required_kwarg, - extract_search_dirs, MutableModuleObjectHolder) from .dependencyfallbacks import DependencyFallbacksHolder +from .interpreterobjects import ( + SubprojectHolder, + EnvironmentVariablesObject, + ConfigurationDataObject, + Test, + RunProcess, + extract_required_kwarg, + extract_search_dirs, + NullSubprojectInterpreter, +) from pathlib import Path import os @@ -57,18 +59,18 @@ import re import stat import collections import typing as T - +import textwrap import importlib if T.TYPE_CHECKING: from . import kwargs # Input source types passed to Targets - SourceInputs = T.Union[mesonlib.File, GeneratedListHolder, TargetHolder, - CustomTargetIndexHolder, GeneratedObjectsHolder, str] + SourceInputs = T.Union[mesonlib.File, build.GeneratedList, build.BuildTarget, + build.CustomTargetIndex, build.CustomTarget, build.GeneratedList, str] # Input source types passed to the build.Target5 classes SourceOutputs = T.Union[mesonlib.File, build.GeneratedList, - build.BuildTarget, build.CustomTargetIndex, + build.BuildTarget, build.CustomTargetIndex, build.CustomTarget, build.GeneratedList] @@ -126,7 +128,6 @@ class Summary: raise InterpreterException(f'Summary section {section!r} already have key {k!r}') formatted_values = [] for i in listify(v): - i = unholder(i) if isinstance(i, bool) and bool_yn: formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) elif isinstance(i, (str, int, bool)): @@ -190,7 +191,7 @@ known_build_target_kwargs = ( ) TEST_KWARGS: T.List[KwargInfo] = [ - KwargInfo('args', ContainerTypeInfo(list, (str, mesonlib.File, TargetHolder)), + KwargInfo('args', ContainerTypeInfo(list, (str, mesonlib.File, build.Target)), listify=True, default=[]), KwargInfo('should_fail', bool, default=False), KwargInfo('timeout', int, default=30), @@ -200,11 +201,11 @@ TEST_KWARGS: T.List[KwargInfo] = [ default='exitcode', validator=lambda x: 'value must be one of "exitcode", "tap", "gtest", "rust"' if x not in {'exitcode', 'tap', 'gtest', 'rust'} else None, since_values={'gtest': '0.55.0', 'rust': '0.57.0'}), - KwargInfo('depends', ContainerTypeInfo(list, (CustomTargetHolder, BuildTargetHolder)), + KwargInfo('depends', ContainerTypeInfo(list, (build.CustomTarget, build.BuildTarget)), listify=True, default=[], since='0.46.0'), KwargInfo('priority', int, default=0, since='0.52.0'), # TODO: env needs reworks of the way the environment variable holder itself works probably - KwargInfo('env', (EnvironmentVariablesHolder, list, dict, str)), + KwargInfo('env', (EnvironmentVariablesObject, list, dict, str)), KwargInfo('suite', ContainerTypeInfo(list, str), listify=True, default=['']), # yes, a list of empty string ] @@ -230,11 +231,11 @@ permitted_dependency_kwargs = { 'version', } -class Interpreter(InterpreterBase): +class Interpreter(InterpreterBase, HoldableObject): def __init__( self, - build: build.Build, + _build: build.Build, backend: T.Optional[Backend] = None, subproject: str = '', subdir: str = '', @@ -245,10 +246,10 @@ class Interpreter(InterpreterBase): ast: T.Optional[mparser.CodeBlockNode] = None, is_translated: bool = False, ) -> None: - super().__init__(build.environment.get_source_dir(), subdir, subproject) + super().__init__(_build.environment.get_source_dir(), subdir, subproject) self.an_unpicklable_object = mesonlib.an_unpicklable_object - self.build = build - self.environment = build.environment + self.build = _build + self.environment = self.build.environment self.coredata = self.environment.get_coredata() self.backend = backend self.summary = {} @@ -267,12 +268,12 @@ class Interpreter(InterpreterBase): elif ast is not None: self.ast = ast self.sanity_check_ast() - self.builtin.update({'meson': MesonMain(build, self)}) - self.generators: T.List['GeneratorHolder'] = [] + self.builtin.update({'meson': MesonMain(self.build, self)}) + self.generators: T.List[build.Generator] = [] self.processed_buildfiles = set() # type: T.Set[str] self.project_args_frozen = False self.global_args_frozen = False # implies self.project_args_frozen - self.subprojects = {} + self.subprojects: T.Dict[str, SubprojectHolder] = {} self.subproject_stack = [] self.configure_file_outputs = {} # Passed from the outside, only used in subprojects. @@ -282,6 +283,7 @@ class Interpreter(InterpreterBase): self.default_project_options = {} self.project_default_options = {} self.build_func_dict() + self.build_holder_map() # build_def_files needs to be defined before parse_project is called # @@ -308,11 +310,11 @@ class Interpreter(InterpreterBase): assert self.build.environment.machines.target.cpu is not None self.builtin['build_machine'] = \ - MachineHolder(self.build.environment.machines.build) + OBJ.MachineHolder(self.build.environment.machines.build, self) self.builtin['host_machine'] = \ - MachineHolder(self.build.environment.machines.host) + OBJ.MachineHolder(self.build.environment.machines.host, self) self.builtin['target_machine'] = \ - MachineHolder(self.build.environment.machines.target) + OBJ.MachineHolder(self.build.environment.machines.target, self) # TODO: Why is this in interpreter.py and not CoreData or Environment? def get_non_matching_default_options(self) -> T.Iterator[T.Tuple[str, str, coredata.UserOption]]: @@ -386,45 +388,70 @@ class Interpreter(InterpreterBase): if 'MESON_UNIT_TEST' in os.environ: self.funcs.update({'exception': self.func_exception}) - def holderify(self, item): - if isinstance(item, list): - return [self.holderify(x) for x in item] - if isinstance(item, dict): - return {k: self.holderify(v) for k, v in item.items()} - - if isinstance(item, build.CustomTarget): - return CustomTargetHolder(item, self) - elif isinstance(item, (int, str, bool, Disabler, InterpreterObject, mesonlib.File)) or item is None: - return item - elif isinstance(item, build.Executable): - return ExecutableHolder(item, self) - elif isinstance(item, build.GeneratedList): - return GeneratedListHolder(item) - elif isinstance(item, build.RunTarget): - raise RuntimeError('This is not a pipe.') - elif isinstance(item, ExecutableSerialisation): - raise RuntimeError('Do not do this.') - elif isinstance(item, build.Data): - return DataHolder(item) - elif isinstance(item, dependencies.Dependency): - return DependencyHolder(item, self.subproject) - elif isinstance(item, ExternalProgram): - return ExternalProgramHolder(item, self.subproject) - elif isinstance(item, MutableModuleObject): - return MutableModuleObjectHolder(item, self) - elif isinstance(item, ModuleObject): - return ModuleObjectHolder(item, self) - elif isinstance(item, (InterpreterObject, ObjectHolder)): - return item - else: - raise InterpreterException('Module returned a value of unknown type.') - - def process_new_values(self, invalues): + def build_holder_map(self) -> None: + ''' + Build a mapping of `HoldableObject` types to their corresponding + `ObjectHolder`s. This mapping is used in `InterpreterBase` to automatically + holderify all returned values from methods and functions. + ''' + self.holder_map.update({ + mesonlib.File: OBJ.FileHolder, + build.SharedLibrary: OBJ.SharedLibraryHolder, + build.StaticLibrary: OBJ.StaticLibraryHolder, + build.BothLibraries: OBJ.BothLibrariesHolder, + build.SharedModule: OBJ.SharedModuleHolder, + build.Executable: OBJ.ExecutableHolder, + build.Jar: OBJ.JarHolder, + build.CustomTarget: OBJ.CustomTargetHolder, + build.CustomTargetIndex: OBJ.CustomTargetIndexHolder, + build.Generator: OBJ.GeneratorHolder, + build.GeneratedList: OBJ.GeneratedListHolder, + build.ExtractedObjects: OBJ.GeneratedObjectsHolder, + build.RunTarget: OBJ.RunTargetHolder, + build.AliasTarget: OBJ.AliasTargetHolder, + build.Headers: OBJ.HeadersHolder, + build.Man: OBJ.ManHolder, + build.Data: OBJ.DataHolder, + build.InstallDir: OBJ.InstallDirHolder, + build.IncludeDirs: OBJ.IncludeDirsHolder, + compilers.RunResult: compilerOBJ.TryRunResultHolder, + dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder, + coredata.UserFeatureOption: OBJ.FeatureOptionHolder, + }) + + ''' + Build a mapping of `HoldableObject` base classes to their + corresponding `ObjectHolder`s. The difference to `self.holder_map` + is that the keys here define an upper bound instead of requireing an + exact match. + + The mappings defined here are only used when there was no direct hit + found in `self.holder_map`. + ''' + self.bound_holder_map.update({ + dependencies.Dependency: OBJ.DependencyHolder, + ExternalProgram: OBJ.ExternalProgramHolder, + compilers.Compiler: compilerOBJ.CompilerHolder, + ModuleObject: OBJ.ModuleObjectHolder, + MutableModuleObject: OBJ.MutableModuleObjectHolder, + }) + + def append_holder_map(self, held_type: T.Type[mesonlib.HoldableObject], holder_type: T.Type[ObjectHolder]) -> None: + ''' + Adds one additional mapping to the `holder_map`. + + The intended use for this function is in the `initialize` method of + modules to register custom object holders. + ''' + self.holder_map.update({ + held_type: holder_type + }) + + def process_new_values(self, invalues: T.List[TYPE_var]) -> None: invalues = listify(invalues) for v in invalues: - if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)): - v = v.held_object - + if isinstance(v, ObjectHolder): + raise InterpreterException('Modules must not return ObjectHolders') if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)): self.add_target(v.name, v) elif isinstance(v, list): @@ -538,7 +565,7 @@ class Interpreter(InterpreterBase): mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) modname = 'unstable_' + plainname self.import_module(modname) - return ModuleObjectHolder(self.modules[modname], self) + return self.modules[modname] @stringArgs @noKwargs @@ -582,20 +609,16 @@ class Interpreter(InterpreterBase): if not isinstance(version, str): raise InterpreterException('Version must be a string.') incs = self.extract_incdirs(kwargs) - libs = unholder(extract_as_list(kwargs, 'link_with')) - libs_whole = unholder(extract_as_list(kwargs, 'link_whole')) + libs = extract_as_list(kwargs, 'link_with') + libs_whole = extract_as_list(kwargs, 'link_whole') sources = extract_as_list(kwargs, 'sources') - sources = unholder(listify(self.source_strings_to_files(sources))) - deps = unholder(extract_as_list(kwargs, 'dependencies')) + sources = listify(self.source_strings_to_files(sources)) + deps = extract_as_list(kwargs, 'dependencies') compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) variables = self.extract_variables(kwargs, list_new=True) final_deps = [] for d in deps: - try: - d = d.held_object - except Exception: - pass if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): raise InterpreterException('Dependencies must be external deps') final_deps.append(d) @@ -606,7 +629,7 @@ external dependencies (including libraries) must go to "dependencies".''') dep = dependencies.InternalDependency(version, incs, compile_args, link_args, libs, libs_whole, sources, final_deps, variables) - return DependencyHolder(dep, self.subproject) + return dep @noKwargs def func_assert(self, node, args, kwargs): @@ -646,7 +669,11 @@ external dependencies (including libraries) must go to "dependencies".''') def func_run_command(self, node, args, kwargs): return self.run_command_impl(node, args, kwargs) - def run_command_impl(self, node, args, kwargs, in_builddir=False): + def run_command_impl(self, + node: mparser.BaseNode, + args: T.Sequence[TYPE_nvar], + kwargs: TYPE_nkwargs, + in_builddir: bool = False) -> RunProcess: if len(args) < 1: raise InterpreterException('Not enough arguments') cmd, *cargs = args @@ -663,17 +690,16 @@ external dependencies (including libraries) must go to "dependencies".''') m = 'must be a string, or the output of find_program(), files() '\ 'or configure_file(), or a compiler object; not {!r}' expanded_args = [] - if isinstance(cmd, ExternalProgramHolder): - cmd = cmd.held_object - if isinstance(cmd, build.Executable): - progname = node.args.arguments[0].value - msg = 'Program {!r} was overridden with the compiled executable {!r}'\ - ' and therefore cannot be used during configuration' - raise InterpreterException(msg.format(progname, cmd.description())) + if isinstance(cmd, build.Executable): + progname = node.args.arguments[0].value + msg = 'Program {!r} was overridden with the compiled executable {!r}'\ + ' and therefore cannot be used during configuration' + raise InterpreterException(msg.format(progname, cmd.description())) + if isinstance(cmd, ExternalProgram): if not cmd.found(): raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable') - elif isinstance(cmd, CompilerHolder): - exelist = cmd.compiler.get_exelist() + elif isinstance(cmd, compilers.Compiler): + exelist = cmd.get_exelist() cmd = exelist[0] prog = ExternalProgram(cmd, silent=True) if not prog.found(): @@ -698,8 +724,8 @@ external dependencies (including libraries) must go to "dependencies".''') expanded_args.append(a) elif isinstance(a, mesonlib.File): expanded_args.append(a.absolute_path(srcdir, builddir)) - elif isinstance(a, ExternalProgramHolder): - expanded_args.append(a.held_object.get_path()) + elif isinstance(a, ExternalProgram): + expanded_args.append(a.get_path()) else: raise InterpreterException('Arguments ' + m.format(a)) # If any file that was used as an argument to the command @@ -727,10 +753,16 @@ external dependencies (including libraries) must go to "dependencies".''') if len(args) != 1: raise InterpreterException('Subproject takes exactly one argument') subp_name = args[0] - return self.do_subproject(subp_name, 'meson', kwargs) + subp = self.do_subproject(subp_name, 'meson', kwargs) + # Update the holder maps from the subproject. Additional entries to the + # holder maps can be added through imported Meson modules + if isinstance(subp.held_object, Interpreter): + self.holder_map.update(subp.held_object.holder_map) + self.bound_holder_map.update(subp.held_object.bound_holder_map) + return subp def disabled_subproject(self, subp_name, disabled_feature=None, exception=None): - sub = SubprojectHolder(None, os.path.join(self.subproject_dir, subp_name), + sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name), disabled_feature=disabled_feature, exception=exception) self.subprojects[subp_name] = sub self.coredata.initialized_subprojects.add(subp_name) @@ -815,6 +847,8 @@ external dependencies (including libraries) must go to "dependencies".''') subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir, self.modules, default_options, ast=ast, is_translated=is_translated) subi.subprojects = self.subprojects + subi.holder_map.update(self.holder_map) + subi.bound_holder_map.update(self.bound_holder_map) subi.subproject_stack = self.subproject_stack + [subp_name] current_active = self.active_projectname @@ -853,7 +887,7 @@ external dependencies (including libraries) must go to "dependencies".''') prefix = self.coredata.options[OptionKey('prefix')].value from ..modules.cmake import CMakeSubprojectOptions - options = unholder(kwargs.get('options', CMakeSubprojectOptions())) + options = kwargs.get('options', CMakeSubprojectOptions()) if not isinstance(options, CMakeSubprojectOptions): raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions' ' object (created by cmake.subproject_options())') @@ -937,7 +971,8 @@ external dependencies (including libraries) must go to "dependencies".''') 'options of other subprojects.') opt = self.get_option_internal(optname) if isinstance(opt, coredata.UserFeatureOption): - return FeatureOptionHolder(self.environment, optname, opt) + opt.name = optname + return opt elif isinstance(opt, coredata.UserOption): return opt.value return opt @@ -953,7 +988,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('configuration_data first argument must be a dictionary') else: initial_values = {} - return ConfigurationDataHolder(self.subproject, initial_values) + return ConfigurationDataObject(self.subproject, initial_values) def set_backend(self): # The backend is already set when parsing subprojects @@ -1263,14 +1298,14 @@ external dependencies (including libraries) must go to "dependencies".''') return success def program_from_file_for(self, for_machine, prognames): - for p in unholder(prognames): + for p in prognames: if isinstance(p, mesonlib.File): continue # Always points to a local (i.e. self generated) file. if not isinstance(p, str): raise InterpreterException('Executable name must be a string') prog = ExternalProgram.from_bin_list(self.environment, for_machine, p) if prog.found(): - return ExternalProgramHolder(prog, self.subproject) + return prog return None def program_from_system(self, args, search_dirs, extra_info): @@ -1297,10 +1332,9 @@ external dependencies (including libraries) must go to "dependencies".''') extprog = ExternalProgram(exename, search_dir=search_dir, extra_search_dirs=extra_search_dirs, silent=True) - progobj = ExternalProgramHolder(extprog, self.subproject) - if progobj.found(): - extra_info.append(f"({' '.join(progobj.get_command())})") - return progobj + if extprog.found(): + extra_info.append(f"({' '.join(extprog.get_command())})") + return extprog def program_from_overrides(self, command_names, extra_info): for name in command_names: @@ -1309,7 +1343,7 @@ external dependencies (including libraries) must go to "dependencies".''') if name in self.build.find_overrides: exe = self.build.find_overrides[name] extra_info.append(mlog.blue('(overridden)')) - return ExternalProgramHolder(exe, self.subproject, self.backend) + return exe return None def store_name_lookups(self, command_names): @@ -1327,7 +1361,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.find_overrides[name] = exe def notfound_program(self, args): - return ExternalProgramHolder(NonExistingExternalProgram(' '.join(args)), self.subproject) + return NonExistingExternalProgram(' '.join(args)) # TODO update modules to always pass `for_machine`. It is bad-form to assume # the host machine. @@ -1341,7 +1375,7 @@ external dependencies (including libraries) must go to "dependencies".''') if progobj is None: progobj = self.notfound_program(args) - if not progobj.found(): + if isinstance(progobj, ExternalProgram) and not progobj.found(): mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO')) if required: m = 'Program {!r} not found' @@ -1351,22 +1385,28 @@ external dependencies (including libraries) must go to "dependencies".''') if wanted: if version_func: version = version_func(progobj) - else: + elif isinstance(progobj, build.Executable): + interp = self + if progobj.subproject: + interp = self.subprojects[progobj.subproject].held_object + assert isinstance(interp, Interpreter) + version = interp.project_version + elif isinstance(progobj, ExternalProgram): version = progobj.get_version(self) is_found, not_found, found = mesonlib.version_compare_many(version, wanted) if not is_found: - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), + mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'), 'found', mlog.normal_cyan(version), 'but need:', mlog.bold(', '.join([f"'{e}'" for e in not_found])), *extra_info) if required: m = 'Invalid version of program, need {!r} {!r} found {!r}.' - raise InterpreterException(m.format(progobj.get_name(), not_found, version)) + raise InterpreterException(m.format(progobj.name, not_found, version)) return self.notfound_program(args) extra_info.insert(0, mlog.normal_cyan(version)) # Only store successful lookups self.store_name_lookups(args) - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.green('YES'), *extra_info) + mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.green('YES'), *extra_info) return progobj def program_lookup(self, args, for_machine, required, search_dirs, extra_info): @@ -1386,7 +1426,7 @@ external dependencies (including libraries) must go to "dependencies".''') progobj = self.program_from_system(args, search_dirs, extra_info) if progobj is None and args[0].endswith('python3'): prog = ExternalProgram('python3', mesonlib.python_command, silent=True) - progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None + progobj = prog if prog.found() else None if progobj is None and fallback and required: progobj = self.find_program_fallback(fallback, args, required, extra_info) @@ -1457,17 +1497,19 @@ external dependencies (including libraries) must go to "dependencies".''') if not_found_message: self.message_impl([not_found_message]) raise - assert isinstance(d, DependencyHolder) + assert isinstance(d, Dependency) if not d.found() and not_found_message: self.message_impl([not_found_message]) self.message_impl([not_found_message]) # Ensure the correct include type if 'include_type' in kwargs: wanted = kwargs['include_type'] - actual = d.include_type_method([], {}) + if not isinstance(wanted, str): + raise InvalidArguments('The `include_type` kwarg must be a string') + actual = d.get_include_type() if wanted != actual: mlog.debug(f'Current include type of {names[0]} is {actual}. Converting to requested {wanted}') - d = d.as_system_method([wanted], {}) + d = d.generate_system_dependency(wanted) return d @FeatureNew('disabler', '0.44.0') @@ -1481,16 +1523,16 @@ external dependencies (including libraries) must go to "dependencies".''') @FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.") @permittedKwargs(build.known_exe_kwargs) def func_executable(self, node, args, kwargs): - return self.build_target(node, args, kwargs, ExecutableHolder) + return self.build_target(node, args, kwargs, build.Executable) @permittedKwargs(build.known_stlib_kwargs) def func_static_lib(self, node, args, kwargs): - return self.build_target(node, args, kwargs, StaticLibraryHolder) + return self.build_target(node, args, kwargs, build.StaticLibrary) @permittedKwargs(build.known_shlib_kwargs) def func_shared_lib(self, node, args, kwargs): - holder = self.build_target(node, args, kwargs, SharedLibraryHolder) - holder.held_object.shared_library_only = True + holder = self.build_target(node, args, kwargs, build.SharedLibrary) + holder.shared_library_only = True return holder @permittedKwargs(known_library_kwargs) @@ -1500,7 +1542,7 @@ external dependencies (including libraries) must go to "dependencies".''') @FeatureNew('shared_module', '0.37.0') @permittedKwargs(build.known_shmod_kwargs) def func_shared_module(self, node, args, kwargs): - return self.build_target(node, args, kwargs, SharedModuleHolder) + return self.build_target(node, args, kwargs, build.SharedModule) @permittedKwargs(known_library_kwargs) def func_library(self, node, args, kwargs): @@ -1508,7 +1550,7 @@ external dependencies (including libraries) must go to "dependencies".''') @permittedKwargs(build.known_jar_kwargs) def func_jar(self, node, args, kwargs): - return self.build_target(node, args, kwargs, JarHolder) + return self.build_target(node, args, kwargs, build.Jar) @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options']) @permittedKwargs(known_build_target_kwargs) @@ -1517,21 +1559,21 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Missing target_type keyword argument') target_type = kwargs.pop('target_type') if target_type == 'executable': - return self.build_target(node, args, kwargs, ExecutableHolder) + return self.build_target(node, args, kwargs, build.Executable) elif target_type == 'shared_library': - return self.build_target(node, args, kwargs, SharedLibraryHolder) + return self.build_target(node, args, kwargs, build.SharedLibrary) elif target_type == 'shared_module': FeatureNew('build_target(target_type: \'shared_module\')', '0.51.0').use(self.subproject) - return self.build_target(node, args, kwargs, SharedModuleHolder) + return self.build_target(node, args, kwargs, build.SharedModule) elif target_type == 'static_library': - return self.build_target(node, args, kwargs, StaticLibraryHolder) + return self.build_target(node, args, kwargs, build.StaticLibrary) elif target_type == 'both_libraries': return self.build_both_libraries(node, args, kwargs) elif target_type == 'library': return self.build_library(node, args, kwargs) elif target_type == 'jar': - return self.build_target(node, args, kwargs, JarHolder) + return self.build_target(node, args, kwargs, build.Jar) else: raise InterpreterException('Unknown target_type.') @@ -1613,8 +1655,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if 'command' in kwargs and isinstance(kwargs['command'], list) and kwargs['command']: if isinstance(kwargs['command'][0], str): kwargs['command'][0] = self.func_find_program(node, kwargs['command'][0], {}) - tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self) - self.add_target(name, tg.held_object) + tg = build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend) + self.add_target(name, tg) return tg @FeatureNewKwargs('run_target', '0.57.0', ['env']) @@ -1626,12 +1668,12 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if 'command' not in kwargs: raise InterpreterException('Missing "command" keyword argument') all_args = extract_as_list(kwargs, 'command') - deps = unholder(extract_as_list(kwargs, 'depends')) + deps = extract_as_list(kwargs, 'depends') else: raise InterpreterException('Run_target needs at least one positional argument.') cleaned_args = [] - for i in unholder(listify(all_args)): + for i in listify(all_args): if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, ExternalProgram, mesonlib.File)): mlog.debug('Wrong type:', str(i)) raise InterpreterException('Invalid argument to run_target.') @@ -1649,8 +1691,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self raise InterpreterException('Depends items must be build targets.') cleaned_deps.append(d) env = self.unpack_env_kwarg(kwargs) - tg = RunTargetHolder(build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env), self) - self.add_target(name, tg.held_object) + tg = build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env) + self.add_target(name, tg) full_name = (self.subproject, name) assert(full_name not in self.build.run_target_names) self.build.run_target_names.add(full_name) @@ -1664,28 +1706,28 @@ This will become a hard error in the future.''' % kwargs['input'], location=self name = args[0] if not isinstance(name, str): raise InterpreterException('First argument must be a string.') - deps = unholder(listify(args[1:])) + deps = listify(args[1:]) for d in deps: if not isinstance(d, (build.BuildTarget, build.CustomTarget)): raise InterpreterException('Depends items must be build targets.') - tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self) - self.add_target(name, tg.held_object) + tg = build.AliasTarget(name, deps, self.subdir, self.subproject) + self.add_target(name, tg) return tg @permittedKwargs({'arguments', 'output', 'depends', 'depfile', 'capture', 'preserve_path_from'}) - @typed_pos_args('generator', (ExecutableHolder, ExternalProgramHolder)) + @typed_pos_args('generator', (build.Executable, ExternalProgram)) @typed_kwargs( 'generator', KwargInfo('arguments', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), KwargInfo('output', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), KwargInfo('depfile', str, validator=lambda x: 'Depfile must be a plain filename with a subdirectory' if has_path_sep(x) else None), KwargInfo('capture', bool, default=False, since='0.43.0'), - KwargInfo('depends', ContainerTypeInfo(list, (BuildTargetHolder, CustomTargetHolder)), default=[], listify=True), + KwargInfo('depends', ContainerTypeInfo(list, (build.BuildTarget, build.CustomTarget)), default=[], listify=True), ) def func_generator(self, node: mparser.FunctionNode, - args: T.Tuple[T.Union[ExecutableHolder, ExternalProgramHolder]], - kwargs: 'kwargs.FuncGenerator') -> GeneratorHolder: + args: T.Tuple[T.Union[build.Executable, ExternalProgram]], + kwargs: 'kwargs.FuncGenerator') -> build.Generator: for rule in kwargs['output']: if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') @@ -1696,43 +1738,40 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if '@OUTPUT@' in o: raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') - depends = [d.held_object for d in kwargs.pop('depends')] - - gen = build.Generator(args[0].held_object, depends=depends, **kwargs) - holder = GeneratorHolder(gen, self) - self.generators.append(holder) - return holder + gen = build.Generator(args[0], **kwargs) + self.generators.append(gen) + return gen - @typed_pos_args('benchmark', str, (ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File)) + @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File)) @typed_kwargs('benchmark', *TEST_KWARGS) def func_benchmark(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], kwargs: 'kwargs.FuncBenchmark') -> None: self.add_test(node, args, kwargs, False) - @typed_pos_args('test', str, (ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File)) + @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File)) @typed_kwargs('benchmark', *TEST_KWARGS, KwargInfo('is_parallel', bool, default=True)) def func_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], kwargs: 'kwargs.FuncTest') -> None: self.add_test(node, args, kwargs, True) - def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariablesHolder, T.Dict[str, str], T.List[str]]) -> build.EnvironmentVariables: - envlist = kwargs.get('env', EnvironmentVariablesHolder()) - if isinstance(envlist, EnvironmentVariablesHolder): - env = envlist.held_object + 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 = EnvironmentVariablesHolder(envlist) - env = env.held_object + env = EnvironmentVariablesObject(envlist) + env = env.vars else: # Convert from array to environment object - env = EnvironmentVariablesHolder(envlist) - env = env.held_object + env = EnvironmentVariablesObject(envlist) + env = env.vars return env def make_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], kwargs: 'kwargs.BaseTest') -> Test: name = args[0] if ':' in name: @@ -1759,10 +1798,10 @@ This will become a hard error in the future.''' % kwargs['input'], location=self return Test(name, prj, suite, - exe.held_object, - [d.held_object for d in kwargs['depends']], + exe, + kwargs['depends'], kwargs.get('is_parallel', False), - [c.held_object if isinstance(c, ObjectHolder) else c for c in kwargs['args']], + kwargs['args'], env, kwargs['should_fail'], kwargs['timeout'], @@ -1798,7 +1837,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self h = build.Headers(source_files, install_subdir, install_dir, install_mode, self.subproject) self.build.headers.append(h) - return HeadersHolder(h) + return h @FeatureNewKwargs('install_man', '0.47.0', ['install_mode']) @FeatureNewKwargs('install_man', '0.58.0', ['locale']) @@ -1821,7 +1860,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self m = build.Man(sources, custom_install_dir, custom_install_mode, self.subproject, locale) self.build.man.append(m) - return ManHolder(m) + return m @FeatureNewKwargs('subdir', '0.44.0', ['if_found']) @permittedKwargs({'if_found'}) @@ -1835,9 +1874,9 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if self.subdir == '' and args[0].startswith('meson-'): raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().') for i in mesonlib.extract_as_list(kwargs, 'if_found'): - if not hasattr(i, 'found_method'): + if not hasattr(i, 'found'): raise InterpreterException('Object used in if_found does not have a found method.') - if not i.found_method([], {}): + if not i.found(): return prev_subdir = self.subdir subdir = os.path.join(prev_subdir, args[0]) @@ -1919,8 +1958,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self '"rename" and "sources" argument lists must be the same length if "rename" is given. ' f'Rename has {len(rename)} elements and sources has {len(sources)}.') - data = DataHolder(build.Data(sources, install_dir, install_mode, self.subproject, rename)) - self.build.data.append(data.held_object) + data = build.Data(sources, install_dir, install_mode, self.subproject, rename) + self.build.data.append(data) return data @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories']) @@ -1968,7 +2007,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self install_mode = self._get_kwarg_install_mode(kwargs) idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory, self.subproject) self.build.install_dirs.append(idir) - return InstallDirHolder(idir) + return idir @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding']) @FeatureNewKwargs('configure_file', '0.46.0', ['format']) @@ -2065,8 +2104,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self conf = kwargs['configuration'] if isinstance(conf, dict): FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject) - conf = ConfigurationDataHolder(self.subproject, conf) - elif not isinstance(conf, ConfigurationDataHolder): + conf = ConfigurationDataObject(self.subproject, conf) + elif not isinstance(conf, ConfigurationDataObject): raise InterpreterException('Argument "configuration" is not of type configuration_data') mlog.log('Configuring', mlog.bold(output), 'using configuration') if len(inputs) > 1: @@ -2075,7 +2114,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) file_encoding = kwargs.setdefault('encoding', 'utf-8') missing_variables, confdata_useless = \ - mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object, + mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.conf_data, fmt, file_encoding) if missing_variables: var_list = ", ".join(map(repr, sorted(missing_variables))) @@ -2090,7 +2129,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self 'copy a file to the build dir, use the \'copy:\' keyword ' 'argument added in 0.47.0'.format(ifbase), location=node) else: - mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format) + mesonlib.dump_conf_header(ofile_abs, conf.conf_data, output_format) conf.mark_used() elif 'command' in kwargs: if len(inputs) > 1: @@ -2162,13 +2201,13 @@ This will become a hard error in the future.''' % kwargs['input'], location=self return mesonlib.File.from_built_file(self.subdir, output) def extract_incdirs(self, kwargs): - prospectives = unholder(extract_as_list(kwargs, 'include_directories')) + prospectives = extract_as_list(kwargs, 'include_directories') result = [] for p in prospectives: if isinstance(p, build.IncludeDirs): result.append(p) elif isinstance(p, str): - result.append(self.build_incdir_object([p]).held_object) + result.append(self.build_incdir_object([p])) else: raise InterpreterException('Include directory objects can only be created from strings or include directories.') return result @@ -2178,7 +2217,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self def func_include_directories(self, node, args, kwargs): return self.build_incdir_object(args, kwargs.get('is_system', False)) - def build_incdir_object(self, incdir_strings, is_system=False): + def build_incdir_object(self, incdir_strings: T.List[str], is_system: bool = False) -> build.IncludeDirs: if not isinstance(is_system, bool): raise InvalidArguments('Is_system must be boolean.') src_root = self.environment.get_source_dir() @@ -2188,51 +2227,52 @@ This will become a hard error in the future.''' % kwargs['input'], location=self for a in incdir_strings: if a.startswith(src_root): - raise InvalidArguments('Tried to form an absolute path to a source dir. ' - 'You should not do that but use relative paths instead.' - ''' + raise InvalidArguments(textwrap.dedent('''\ + Tried to form an absolute path to a source dir. + You should not do that but use relative paths instead. -To get include path to any directory relative to the current dir do + To get include path to any directory relative to the current dir do -incdir = include_directories(dirname) + incdir = include_directories(dirname) -After this incdir will contain both the current source dir as well as the -corresponding build dir. It can then be used in any subdirectory and -Meson will take care of all the busywork to make paths work. + After this incdir will contain both the current source dir as well as the + corresponding build dir. It can then be used in any subdirectory and + Meson will take care of all the busywork to make paths work. -Dirname can even be '.' to mark the current directory. Though you should -remember that the current source and build directories are always -put in the include directories by default so you only need to do -include_directories('.') if you intend to use the result in a -different subdirectory. -''') + Dirname can even be '.' to mark the current directory. Though you should + remember that the current source and build directories are always + put in the include directories by default so you only need to do + include_directories('.') if you intend to use the result in a + different subdirectory. + ''')) else: try: self.validate_within_subproject(self.subdir, a) except InterpreterException: mlog.warning('include_directories sandbox violation!') - print(f'''The project is trying to access the directory {a} which belongs to a different -subproject. This is a problem as it hardcodes the relative paths of these two projeccts. -This makes it impossible to compile the project in any other directory layout and also -prevents the subproject from changing its own directory layout. + print(textwrap.dedent(f'''\ + The project is trying to access the directory {a} which belongs to a different + subproject. This is a problem as it hardcodes the relative paths of these two projeccts. + This makes it impossible to compile the project in any other directory layout and also + prevents the subproject from changing its own directory layout. -Instead of poking directly at the internals the subproject should be executed and -it should set a variable that the caller can then use. Something like: + Instead of poking directly at the internals the subproject should be executed and + it should set a variable that the caller can then use. Something like: -# In subproject -some_dep = declare_depencency(include_directories: include_directories('include')) + # In subproject + some_dep = declare_depencency(include_directories: include_directories('include')) -# In parent project -some_dep = depencency('some') -executable(..., dependencies: [some_dep]) + # In parent project + some_dep = depencency('some') + executable(..., dependencies: [some_dep]) -This warning will become a hard error in a future Meson release. -''') + This warning will become a hard error in a future Meson release. + ''')) absdir_src = os.path.join(absbase_src, a) absdir_build = os.path.join(absbase_build, a) if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build): raise InvalidArguments('Include dir %s does not exist.' % a) - i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system)) + i = build.IncludeDirs(self.subdir, incdir_strings, is_system) return i @permittedKwargs({'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default', @@ -2247,7 +2287,7 @@ This warning will become a hard error in a future Meson release. if ":" not in setup_name: setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name try: - inp = unholder(extract_as_list(kwargs, 'exe_wrapper')) + inp = extract_as_list(kwargs, 'exe_wrapper') exe_wrapper = [] for i in inp: if isinstance(i, str): @@ -2292,7 +2332,7 @@ This warning will become a hard error in a future Meson release. self._add_global_arguments(node, self.build.global_link_args[kwargs['native']], args[0], kwargs) @typed_pos_args('add_project_arguments', varargs=str) - @typed_kwargs('add_global_arguments', _NATIVE_KW, _LANGUAGE_KW) + @typed_kwargs('add_project_arguments', _NATIVE_KW, _LANGUAGE_KW) def func_add_project_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwargs.FuncAddProjectArgs') -> None: self._add_project_arguments(node, self.build.projects_args[kwargs['native']], args[0], kwargs) @@ -2373,7 +2413,7 @@ This warning will become a hard error in a future Meson release. raise InterpreterException('environment first argument must be a dictionary or a list') else: initial_values = {} - return EnvironmentVariablesHolder(initial_values, self.subproject) + return EnvironmentVariablesObject(initial_values, self.subproject) @stringArgs @noKwargs @@ -2458,10 +2498,10 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey results.append(mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s)) elif isinstance(s, mesonlib.File): results.append(s) - elif isinstance(s, (GeneratedListHolder, TargetHolder, - CustomTargetIndexHolder, - GeneratedObjectsHolder)): - results.append(unholder(s)) + elif isinstance(s, (build.GeneratedList, build.BuildTarget, + build.CustomTargetIndex, build.CustomTarget, + build.GeneratedList)): + results.append(s) else: raise InterpreterException(f'Source item is {s!r} instead of ' 'string or File-type object') @@ -2489,7 +2529,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey @FeatureNew('both_libraries', '0.46.0') def build_both_libraries(self, node, args, kwargs): - shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder) + shared_lib = self.build_target(node, args, kwargs, build.SharedLibrary) # Check if user forces non-PIC static library. pic = True @@ -2515,27 +2555,27 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey static_args = [args[0]] static_kwargs = kwargs.copy() static_kwargs['sources'] = [] - static_kwargs['objects'] = shared_holder.held_object.extract_all_objects() + static_kwargs['objects'] = shared_lib.extract_all_objects() else: static_args = args static_kwargs = kwargs - static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder) + static_lib = self.build_target(node, static_args, static_kwargs, build.StaticLibrary) - return BothLibrariesHolder(shared_holder, static_holder, self) + return build.BothLibraries(shared_lib, static_lib) def build_library(self, node, args, kwargs): default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject)) if default_library == 'shared': - return self.build_target(node, args, kwargs, SharedLibraryHolder) + return self.build_target(node, args, kwargs, build.SharedLibrary) elif default_library == 'static': - return self.build_target(node, args, kwargs, StaticLibraryHolder) + return self.build_target(node, args, kwargs, build.StaticLibrary) elif default_library == 'both': return self.build_both_libraries(node, args, kwargs) else: raise InterpreterException('Unknown default_library value: %s.', default_library) - def build_target(self, node, args, kwargs, targetholder): + def build_target(self, node, args, kwargs, targetclass): @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories']) @FeatureNewKwargs('build target', '0.41.0', ['rust_args']) @FeatureNewKwargs('build target', '0.40.0', ['build_by_default']) @@ -2559,18 +2599,8 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey ef = extract_as_list(kwargs, 'extra_files') kwargs['extra_files'] = self.source_strings_to_files(ef) self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) - if targetholder == ExecutableHolder: - targetclass = build.Executable - elif targetholder == SharedLibraryHolder: - targetclass = build.SharedLibrary - elif targetholder == SharedModuleHolder: - targetclass = build.SharedModule - elif targetholder == StaticLibraryHolder: - targetclass = build.StaticLibrary - elif targetholder == JarHolder: - targetclass = build.Jar - else: - mlog.debug('Unknown target type:', str(targetholder)) + if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}: + mlog.debug('Unknown target type:', str(targetclass)) raise RuntimeError('Unreachable code') self.kwarg_strings_to_includedirs(kwargs) @@ -2583,10 +2613,9 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey target.project_version = self.project_version self.add_stdlib_info(target) - l = targetholder(target, self) - self.add_target(name, l.held_object) + self.add_target(name, target) self.project_args_frozen = True - return l + return target def kwarg_strings_to_includedirs(self, kwargs): if 'd_import_dirs' in kwargs: @@ -2646,6 +2675,7 @@ This will become a hard error in the future.''', location=self.current_node) @noKwargs @noArgsFlattening + @unholder_return 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/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 7b59a24..744f69c 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -1,8 +1,8 @@ import os import shlex import subprocess -import re import copy +import textwrap from pathlib import Path, PurePath @@ -13,42 +13,42 @@ from .. import mlog from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule from ..backend.backends import TestProtocol -from ..interpreterbase import (ContainerTypeInfo, InterpreterObject, KwargInfo, - ObjectHolder, MutableInterpreterObject, - FeatureNewKwargs, FeatureNew, FeatureDeprecated, - typed_kwargs, typed_pos_args, stringArgs, - permittedKwargs, noArgsFlattening, noPosargs, - TYPE_var, TYPE_nkwargs, flatten, - InterpreterException, InvalidArguments, - InvalidCode) -from ..interpreterbase.decorators import FeatureCheckBase +from ..interpreterbase import ( + ContainerTypeInfo, KwargInfo, + InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, + FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated, + typed_pos_args, typed_kwargs, KwargInfo, stringArgs, permittedKwargs, + noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs, + flatten, InterpreterException, InvalidArguments, InvalidCode) 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 if T.TYPE_CHECKING: from . import kwargs from .interpreter import Interpreter + from ..environment import Environment + from ..envconfig import MachineInfo -def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', subproject: str, - feature_check: T.Optional['FeatureCheckBase'] = None, +def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', + subproject: str, + feature_check: T.Optional[FeatureCheckBase] = None, default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]: val = kwargs.get('required', default) disabled = False required = False feature: T.Optional[str] = None - if isinstance(val, FeatureOptionHolder): + if isinstance(val, coredata.UserFeatureOption): if not feature_check: feature_check = FeatureNew('User option "feature"', '0.47.0') feature_check.use(subproject) - option = val.held_object feature = val.name - if option.is_disabled(): + if val.is_disabled(): disabled = True - elif option.is_enabled(): + elif val.is_enabled(): required = True elif isinstance(val, bool): required = val @@ -62,9 +62,9 @@ def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', subproject: str, return disabled, required, feature -def extract_search_dirs(kwargs): - search_dirs = mesonlib.stringlistify(kwargs.get('dirs', [])) - search_dirs = [Path(d).expanduser() for d in search_dirs] +def extract_search_dirs(kwargs: T.Dict[str, T.Any]) -> T.List[str]: + search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', [])) + search_dirs = [Path(d).expanduser() for d in search_dirs_str] for d in search_dirs: if mesonlib.is_windows() and d.root.startswith('\\'): # a Unix-path starting with `/` that is not absolute on Windows. @@ -74,14 +74,13 @@ def extract_search_dirs(kwargs): raise InvalidCode(f'Search directory {d} is not an absolute path.') return list(map(str, search_dirs)) -class FeatureOptionHolder(InterpreterObject, ObjectHolder[coredata.UserFeatureOption]): - def __init__(self, env: 'Environment', name: str, option: coredata.UserFeatureOption): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, option) +class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): + def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'): + super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to case here because options is not a TypedDict - self.held_object = T.cast(coredata.UserFeatureOption, env.coredata.options[OptionKey('auto_features')]) - self.name = name + self.held_object = T.cast(coredata.UserFeatureOption, self.env.coredata.options[OptionKey('auto_features')]) + self.held_object.name = option.name self.methods.update({'enabled': self.enabled_method, 'disabled': self.disabled_method, 'allowed': self.allowed_method, @@ -91,34 +90,36 @@ class FeatureOptionHolder(InterpreterObject, ObjectHolder[coredata.UserFeatureOp }) @property - def value(self): + def value(self) -> str: return 'disabled' if not self.held_object else self.held_object.value - def as_disabled(self): - return FeatureOptionHolder(None, self.name, None) + def as_disabled(self) -> coredata.UserFeatureOption: + disabled = copy.deepcopy(self.held_object) + disabled.value = 'disabled' + return disabled @noPosargs - @permittedKwargs({}) - def enabled_method(self, args, kwargs): + @noKwargs + def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'enabled' @noPosargs - @permittedKwargs({}) - def disabled_method(self, args, kwargs): + @noKwargs + def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'disabled' @noPosargs - @permittedKwargs({}) - def allowed_method(self, args, kwargs): + @noKwargs + def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return not self.value == 'disabled' @noPosargs - @permittedKwargs({}) - def auto_method(self, args, kwargs): + @noKwargs + def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'auto' @permittedKwargs({'error_message'}) - def require_method(self, args, kwargs): + def require_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: if len(args) != 1: raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) if not isinstance(args[0], bool): @@ -127,38 +128,57 @@ class FeatureOptionHolder(InterpreterObject, ObjectHolder[coredata.UserFeatureOp if error_message and not isinstance(error_message, str): raise InterpreterException("Error message must be a string.") if args[0]: - return self + return copy.deepcopy(self.held_object) + assert isinstance(error_message, str) if self.value == 'enabled': - prefix = 'Feature {} cannot be enabled'.format(self.name) + prefix = 'Feature {} cannot be enabled'.format(self.held_object.name) prefix = prefix + ': ' if error_message else '' raise InterpreterException(prefix + error_message) return self.as_disabled() - @permittedKwargs({}) - def disable_auto_if_method(self, args, kwargs): + @noKwargs + def disable_auto_if_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: if len(args) != 1: raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) if not isinstance(args[0], bool): raise InvalidArguments('boolean argument expected.') - return self if self.value != 'auto' or not args[0] else self.as_disabled() - - -class RunProcess(InterpreterObject): - - def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True): + return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() + + +class RunProcess(MesonInterpreterObject): + + def __init__(self, + cmd: ExternalProgram, + args: T.List[str], + env: build.EnvironmentVariables, + source_dir: str, + build_dir: str, + subdir: str, + mesonintrospect: T.List[str], + in_builddir: bool = False, + check: bool = False, + capture: bool = True) -> None: super().__init__() if not isinstance(cmd, ExternalProgram): raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') self.capture = capture - pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) - self.returncode = pc.returncode + self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) self.methods.update({'returncode': self.returncode_method, 'stdout': self.stdout_method, 'stderr': self.stderr_method, }) - def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False): + def run_command(self, + cmd: ExternalProgram, + args: T.List[str], + env: build.EnvironmentVariables, + source_dir: str, + build_dir: str, + subdir: str, + mesonintrospect: T.List[str], + in_builddir: bool, + check: bool = False) -> T.Tuple[int, str, str]: command_array = cmd.get_command() + args menv = {'MESON_SOURCE_ROOT': source_dir, 'MESON_BUILD_ROOT': build_dir, @@ -189,29 +209,33 @@ class RunProcess(InterpreterObject): if check and p.returncode != 0: raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode)) - return p, o, e + return p.returncode, o, e except FileNotFoundError: raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) @noPosargs - @permittedKwargs({}) - def returncode_method(self, args, kwargs): + @noKwargs + def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: return self.returncode @noPosargs - @permittedKwargs({}) - def stdout_method(self, args, kwargs): + @noKwargs + def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.stdout @noPosargs - @permittedKwargs({}) - def stderr_method(self, args, kwargs): + @noKwargs + def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.stderr -class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.EnvironmentVariables]): - def __init__(self, initial_values=None, subproject: str = ''): - MutableInterpreterObject.__init__(self) - ObjectHolder.__init__(self, build.EnvironmentVariables(), subproject) +# 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() self.methods.update({'set': self.set_method, 'append': self.append_method, 'prepend': self.prepend_method, @@ -234,18 +258,18 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En def __repr__(self) -> str: repr_str = "<{0}: {1}>" - return repr_str.format(self.__class__.__name__, self.held_object.envvars) + return repr_str.format(self.__class__.__name__, self.vars.envvars) def unpack_separator(self, kwargs: T.Dict[str, T.Any]) -> str: separator = kwargs.get('separator', os.pathsep) if not isinstance(separator, str): - raise InterpreterException("EnvironmentVariablesHolder methods 'separator'" + raise InterpreterException("EnvironmentVariablesObject methods 'separator'" " argument needs to be a string.") return separator def warn_if_has_name(self, name: str) -> None: # Multiple append/prepend operations was not supported until 0.58.0. - if self.held_object.has_name(name): + if self.vars.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) @@ -255,7 +279,7 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En 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.held_object.set(name, values, separator) + self.vars.set(name, values, separator) @stringArgs @permittedKwargs({'separator'}) @@ -264,7 +288,7 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En name, values = args separator = self.unpack_separator(kwargs) self.warn_if_has_name(name) - self.held_object.append(name, values, separator) + self.vars.append(name, values, separator) @stringArgs @permittedKwargs({'separator'}) @@ -273,14 +297,14 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En name, values = args separator = self.unpack_separator(kwargs) self.warn_if_has_name(name) - self.held_object.prepend(name, values, separator) + self.vars.prepend(name, values, separator) -class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder[build.ConfigurationData]): - def __init__(self, pv, initial_values=None): - MutableInterpreterObject.__init__(self) +class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject): + def __init__(self, subproject: str, initial_values: T.Optional[T.Dict[str, T.Any]] = None) -> None: self.used = False # These objects become immutable after use in configure_file. - ObjectHolder.__init__(self, build.ConfigurationData(), pv) + super().__init__(subproject=subproject) + self.conf_data = build.ConfigurationData() self.methods.update({'set': self.set_method, 'set10': self.set10_method, 'set_quoted': self.set_quoted_method, @@ -294,15 +318,15 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder[build.Confi for k, v in initial_values.items(): self.set_method([k, v], {}) elif initial_values: - raise AssertionError('Unsupported ConfigurationDataHolder initial_values') + raise AssertionError('Unsupported ConfigurationDataObject initial_values') - def is_used(self): + def is_used(self) -> bool: return self.used - def mark_used(self): + def mark_used(self) -> None: self.used = True - def validate_args(self, args, kwargs): + def validate_args(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[str, T.Union[str, int, bool], T.Optional[str]]: if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: mlog.deprecation('Passing a list as the single argument to ' 'configuration_data.set is deprecated. This will ' @@ -326,86 +350,101 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder[build.Confi if desc is not None and not isinstance(desc, str): raise InterpreterException('Description must be a string.') - return name, val, desc + # TODO: Remove the cast once we get rid of the deprecation + return name, T.cast(T.Union[str, bool, int], val), desc @noArgsFlattening - def set_method(self, args, kwargs): + def set_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: (name, val, desc) = self.validate_args(args, kwargs) - self.held_object.values[name] = (val, desc) + self.conf_data.values[name] = (val, desc) - def set_quoted_method(self, args, kwargs): + def set_quoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: (name, val, desc) = self.validate_args(args, kwargs) if not isinstance(val, str): raise InterpreterException("Second argument to set_quoted must be a string.") escaped_val = '\\"'.join(val.split('"')) - self.held_object.values[name] = ('"' + escaped_val + '"', desc) + self.conf_data.values[name] = ('"' + escaped_val + '"', desc) - def set10_method(self, args, kwargs): + def set10_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: (name, val, desc) = self.validate_args(args, kwargs) if val: - self.held_object.values[name] = (1, desc) + self.conf_data.values[name] = (1, desc) else: - self.held_object.values[name] = (0, desc) + self.conf_data.values[name] = (0, desc) - def has_method(self, args, kwargs): - return args[0] in self.held_object.values + def has_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return args[0] in self.conf_data.values @FeatureNew('configuration_data.get()', '0.38.0') @noArgsFlattening - def get_method(self, args, kwargs): + def get_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: if len(args) < 1 or len(args) > 2: raise InterpreterException('Get method takes one or two arguments.') + if not isinstance(args[0], str): + raise InterpreterException('The variable name must be a string.') name = args[0] - if name in self.held_object: - return self.held_object.get(name)[0] + if name in self.conf_data: + return self.conf_data.get(name)[0] if len(args) > 1: - return args[1] + # Assertion does not work because setting other values is still + # supported, but deprecated. Use T.cast in the meantime (even though + # this is a lie). + # TODO: Fix this once the deprecation is removed + # assert isinstance(args[1], (int, str, bool)) + return T.cast(T.Union[str, int, bool], args[1]) raise InterpreterException('Entry %s not in configuration data.' % name) @FeatureNew('configuration_data.get_unquoted()', '0.44.0') - def get_unquoted_method(self, args, kwargs): + def get_unquoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: if len(args) < 1 or len(args) > 2: raise InterpreterException('Get method takes one or two arguments.') + if not isinstance(args[0], str): + raise InterpreterException('The variable name must be a string.') name = args[0] - if name in self.held_object: - val = self.held_object.get(name)[0] + if name in self.conf_data: + val = self.conf_data.get(name)[0] elif len(args) > 1: + assert isinstance(args[1], (str, int, bool)) val = args[1] else: raise InterpreterException('Entry %s not in configuration data.' % name) - if val[0] == '"' and val[-1] == '"': + if isinstance(val, str) and val[0] == '"' and val[-1] == '"': return val[1:-1] return val - def get(self, name): - return self.held_object.values[name] # (val, desc) + def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: + return self.conf_data.values[name] @FeatureNew('configuration_data.keys()', '0.57.0') @noPosargs - def keys_method(self, args, kwargs): + def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: return sorted(self.keys()) - def keys(self): - return self.held_object.values.keys() + def keys(self) -> T.List[str]: + return list(self.conf_data.values.keys()) - def merge_from_method(self, args, kwargs): + def merge_from_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: if len(args) != 1: raise InterpreterException('Merge_from takes one positional argument.') - from_object = args[0] - if not isinstance(from_object, ConfigurationDataHolder): + from_object_holder = args[0] + if not isinstance(from_object_holder, ConfigurationDataObject): raise InterpreterException('Merge_from argument must be a configuration data object.') - from_object = from_object.held_object + from_object = from_object_holder.conf_data for k, v in from_object.values.items(): - self.held_object.values[k] = v + self.conf_data.values[k] = v -permitted_partial_dependency_kwargs = { - 'compile_args', 'link_args', 'links', 'includes', 'sources' -} -class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): - def __init__(self, dep: Dependency, pv: str): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, dep, pv) +_PARTIAL_DEP_KWARGS = [ + KwargInfo('compile_args', bool, default=False), + KwargInfo('link_args', bool, default=False), + KwargInfo('links', bool, default=False), + KwargInfo('includes', bool, default=False), + KwargInfo('sources', bool, default=False), +] + +class DependencyHolder(ObjectHolder[Dependency]): + def __init__(self, dep: Dependency, interpreter: 'Interpreter'): + super().__init__(dep, interpreter) self.methods.update({'found': self.found_method, 'type_name': self.type_name_method, 'version': self.version_method, @@ -419,35 +458,35 @@ class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): 'as_link_whole': self.as_link_whole_method, }) - def found(self): + def found(self) -> bool: return self.found_method([], {}) @noPosargs - @permittedKwargs({}) - def type_name_method(self, args, kwargs): + @noKwargs + def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.type_name @noPosargs - @permittedKwargs({}) - def found_method(self, args, kwargs): + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: if self.held_object.type_name == 'internal': return True return self.held_object.found() @noPosargs - @permittedKwargs({}) - def version_method(self, args, kwargs): + @noKwargs + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_version() @noPosargs - @permittedKwargs({}) - def name_method(self, args, kwargs): + @noKwargs + def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_name() @FeatureDeprecated('Dependency.get_pkgconfig_variable', '0.56.0', 'use Dependency.get_variable(pkgconfig : ...) instead') @permittedKwargs({'define_variable', 'default'}) - def pkgconfig_method(self, args, kwargs): + def pkgconfig_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: args = listify(args) if len(args) != 1: raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') @@ -459,8 +498,8 @@ class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): @FeatureNew('dep.get_configtool_variable', '0.44.0') @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0', 'use Dependency.get_variable(configtool : ...) instead') - @permittedKwargs({}) - def configtool_method(self, args, kwargs): + @noKwargs + def configtool_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: args = listify(args) if len(args) != 1: raise InterpreterException('get_configtool_variable takes exactly one argument.') @@ -471,16 +510,16 @@ class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): @FeatureNew('dep.partial_dependency', '0.46.0') @noPosargs - @permittedKwargs(permitted_partial_dependency_kwargs) - def partial_dependency_method(self, args, kwargs): + @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS) + def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) - return DependencyHolder(pdep, self.subproject) + return pdep @FeatureNew('dep.get_variable', '0.51.0') @typed_pos_args('dep.get_variable', optargs=[str]) @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'}) @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal']) - def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> str: + def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Union[str, T.List[str]]: default_varname = args[0] if default_varname is not None: FeatureNew('0.58.0', 'Positional argument to dep.get_variable()').use(self.subproject) @@ -490,155 +529,97 @@ class DependencyHolder(InterpreterObject, ObjectHolder[Dependency]): @FeatureNew('dep.include_type', '0.52.0') @noPosargs - @permittedKwargs({}) - def include_type_method(self, args, kwargs): + @noKwargs + def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_include_type() @FeatureNew('dep.as_system', '0.52.0') - @permittedKwargs({}) - def as_system_method(self, args, kwargs): + @noKwargs + def as_system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: args = listify(args) new_is_system = 'system' if len(args) > 1: raise InterpreterException('as_system takes only one optional value') if len(args) == 1: + if not isinstance(args[0], str): + raise InterpreterException('as_system takes exactly one string parameter') new_is_system = args[0] new_dep = self.held_object.generate_system_dependency(new_is_system) - return DependencyHolder(new_dep, self.subproject) + return new_dep @FeatureNew('dep.as_link_whole', '0.56.0') - @permittedKwargs({}) + @noKwargs @noPosargs - def as_link_whole_method(self, args, kwargs): + def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: if not isinstance(self.held_object, InternalDependency): raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects') new_dep = self.held_object.generate_link_whole_dependency() - return DependencyHolder(new_dep, self.subproject) - -class ExternalProgramHolder(InterpreterObject, ObjectHolder[ExternalProgram]): - def __init__(self, ep: ExternalProgram, subproject: str, backend=None): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, ep) - self.subproject = subproject - self.backend = backend + return new_dep + +class ExternalProgramHolder(ObjectHolder[ExternalProgram]): + def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None: + super().__init__(ep, interpreter) self.methods.update({'found': self.found_method, 'path': self.path_method, 'full_path': self.full_path_method}) - self.cached_version = None @noPosargs - @permittedKwargs({}) - def found_method(self, args, kwargs): + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.found() @noPosargs - @permittedKwargs({}) + @noKwargs @FeatureDeprecated('ExternalProgram.path', '0.55.0', 'use ExternalProgram.full_path() instead') - def path_method(self, args, kwargs): + def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() @noPosargs - @permittedKwargs({}) + @noKwargs @FeatureNew('ExternalProgram.full_path', '0.55.0') - def full_path_method(self, args, kwargs): + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() - def _full_path(self): - exe = self.held_object - if isinstance(exe, build.Executable): - return self.backend.get_target_filename_abs(exe) - return exe.get_path() - - def found(self): - return isinstance(self.held_object, build.Executable) or self.held_object.found() - - def get_command(self): - return self.held_object.get_command() - - def get_name(self): - exe = self.held_object - if isinstance(exe, build.Executable): - return exe.name - return exe.get_name() - - def get_version(self, interpreter): - if isinstance(self.held_object, build.Executable): - return self.held_object.project_version - if not self.cached_version: - raw_cmd = self.get_command() + ['--version'] - cmd = [self, '--version'] - res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) - if res.returncode != 0: - m = 'Running {!r} failed' - raise InterpreterException(m.format(raw_cmd)) - output = res.stdout.strip() - if not output: - output = res.stderr.strip() - match = re.search(r'([0-9][0-9\.]+)', output) - if not match: - m = 'Could not find a version number in output of {!r}' - raise InterpreterException(m.format(raw_cmd)) - self.cached_version = match.group(1) - return self.cached_version - -class ExternalLibraryHolder(InterpreterObject, ObjectHolder[ExternalLibrary]): - def __init__(self, el: ExternalLibrary, pv: str): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, el, pv) + def _full_path(self) -> str: + if not self.found(): + raise InterpreterException('Unable to get the path of a not-found external program') + path = self.held_object.get_path() + assert path is not None + return path + + def found(self) -> bool: + return self.held_object.found() + +class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): + def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'): + super().__init__(el, interpreter) self.methods.update({'found': self.found_method, 'type_name': self.type_name_method, 'partial_dependency': self.partial_dependency_method, }) - def found(self): - return self.held_object.found() - @noPosargs - @permittedKwargs({}) - def type_name_method(self, args, kwargs): + @noKwargs + def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.type_name @noPosargs - @permittedKwargs({}) - def found_method(self, args, kwargs): - return self.found() - - def get_name(self): - return self.held_object.name - - def get_compile_args(self): - return self.held_object.get_compile_args() - - def get_link_args(self): - return self.held_object.get_link_args() - - def get_exe_args(self): - return self.held_object.get_exe_args() + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object.found() @FeatureNew('dep.partial_dependency', '0.46.0') @noPosargs - @permittedKwargs(permitted_partial_dependency_kwargs) - def partial_dependency_method(self, args, kwargs): + @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS) + def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) - return DependencyHolder(pdep, self.subproject) - - -class GeneratedListHolder(InterpreterObject, ObjectHolder[build.GeneratedList]): - def __init__(self, arg1: 'build.GeneratedList'): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, arg1) - - def __repr__(self) -> str: - r = '<{}: {!r}>' - return r.format(self.__class__.__name__, self.held_object.get_outputs()) - + return pdep # A machine that's statically known from the cross file -class MachineHolder(InterpreterObject, ObjectHolder['MachineInfo']): - def __init__(self, machine_info: 'MachineInfo'): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, machine_info) +class MachineHolder(ObjectHolder['MachineInfo']): + def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'): + super().__init__(machine_info, interpreter) self.methods.update({'system': self.system_method, 'cpu': self.cpu_method, 'cpu_family': self.cpu_family_method, @@ -646,101 +627,53 @@ class MachineHolder(InterpreterObject, ObjectHolder['MachineInfo']): }) @noPosargs - @permittedKwargs({}) - def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + @noKwargs + def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.cpu_family @noPosargs - @permittedKwargs({}) - def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + @noKwargs + def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.cpu @noPosargs - @permittedKwargs({}) - def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + @noKwargs + def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.system @noPosargs - @permittedKwargs({}) - def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_nkwargs) -> str: + @noKwargs + def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.endian -class IncludeDirsHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): - def __init__(self, idobj: build.IncludeDirs): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, idobj) - -class HeadersHolder(InterpreterObject, ObjectHolder[build.Headers]): - - def __init__(self, obj: build.Headers): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, obj) - - def set_install_subdir(self, subdir): - self.held_object.install_subdir = subdir - - def get_install_subdir(self): - return self.held_object.install_subdir - - def get_sources(self): - return self.held_object.sources - - def get_custom_install_dir(self): - return self.held_object.custom_install_dir - - def get_custom_install_mode(self): - return self.held_object.custom_install_mode - -class DataHolder(InterpreterObject, ObjectHolder[build.Data]): - def __init__(self, data: build.Data): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, data) - - def get_source_subdir(self): - return self.held_object.source_subdir - - def get_sources(self): - return self.held_object.sources - - def get_install_dir(self): - return self.held_object.install_dir - -class InstallDirHolder(InterpreterObject, ObjectHolder[build.IncludeDirs]): - - def __init__(self, obj: build.InstallDir): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, obj) - -class ManHolder(InterpreterObject, ObjectHolder[build.Man]): +class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]): + pass - def __init__(self, obj: build.Man): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, obj) +class FileHolder(ObjectHolder[mesonlib.File]): + pass - def get_custom_install_dir(self) -> T.Optional[str]: - return self.held_object.custom_install_dir +class HeadersHolder(ObjectHolder[build.Headers]): + pass - def get_custom_install_mode(self) -> T.Optional[FileMode]: - return self.held_object.custom_install_mode +class DataHolder(ObjectHolder[build.Data]): + pass - def locale(self) -> T.Optional[str]: - return self.held_object.locale +class InstallDirHolder(ObjectHolder[build.InstallDir]): + pass - def get_sources(self) -> T.List[mesonlib.File]: - return self.held_object.sources +class ManHolder(ObjectHolder[build.Man]): + pass -class GeneratedObjectsHolder(InterpreterObject, ObjectHolder[build.ExtractedObjects]): - def __init__(self, held_object: build.ExtractedObjects): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, held_object) +class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]): + pass -class Test(InterpreterObject): +class Test(MesonInterpreterObject): def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable, depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables, should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, priority: int): - InterpreterObject.__init__(self) + super().__init__() self.name = name self.suite = suite self.project_name = project @@ -755,18 +688,27 @@ class Test(InterpreterObject): self.protocol = TestProtocol.from_str(protocol) self.priority = priority - def get_exe(self): + def get_exe(self) -> build.Executable: return self.exe - def get_name(self): + def get_name(self) -> str: return self.name -class SubprojectHolder(InterpreterObject, ObjectHolder[T.Optional['Interpreter']]): +class NullSubprojectInterpreter(HoldableObject): + pass + +# TODO: This should really be an `ObjectHolder`, but the additional stuff in this +# class prevents this. Thus, this class should be split into a pure +# `ObjectHolder` and a class specifically for stroing in `Interpreter`. +class SubprojectHolder(MesonInterpreterObject): - def __init__(self, subinterpreter: T.Optional['Interpreter'], subdir: str, warnings=0, disabled_feature=None, - exception=None): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, subinterpreter) + def __init__(self, subinterpreter: T.Union['Interpreter', NullSubprojectInterpreter], + subdir: str, + warnings: int = 0, + disabled_feature: T.Optional[str] = None, + exception: T.Optional[MesonException] = None) -> None: + super().__init__() + self.held_object = subinterpreter self.warnings = warnings self.disabled_feature = disabled_feature self.exception = exception @@ -776,19 +718,20 @@ class SubprojectHolder(InterpreterObject, ObjectHolder[T.Optional['Interpreter'] }) @noPosargs - @permittedKwargs({}) - def found_method(self, args, kwargs): + @noKwargs + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.found() - def found(self): - return self.held_object is not None + def found(self) -> bool: + return not isinstance(self.held_object, NullSubprojectInterpreter) - @permittedKwargs({}) + @noKwargs @noArgsFlattening - def get_variable_method(self, args, kwargs): + @unholder_return + def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if len(args) < 1 or len(args) > 2: raise InterpreterException('Get_variable takes one or two arguments.') - if not self.found(): + if isinstance(self.held_object, NullSubprojectInterpreter): # == not self.found() raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir)) varname = args[0] if not isinstance(varname, str): @@ -803,13 +746,8 @@ class SubprojectHolder(InterpreterObject, ObjectHolder[T.Optional['Interpreter'] raise InvalidArguments(f'Requested variable "{varname}" not found.') -class ModuleObjectHolder(InterpreterObject, ObjectHolder['ModuleObject']): - def __init__(self, modobj: 'ModuleObject', interpreter: 'Interpreter'): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, modobj) - self.interpreter = interpreter - - def method_call(self, method_name, args, kwargs): +class ModuleObjectHolder(ObjectHolder[ModuleObject]): + def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: modobj = self.held_object method = modobj.methods.get(method_name) if not method: @@ -827,27 +765,18 @@ class ModuleObjectHolder(InterpreterObject, ObjectHolder['ModuleObject']): if isinstance(ret, ModuleReturnValue): self.interpreter.process_new_values(ret.new_objects) ret = ret.return_value - return self.interpreter.holderify(ret) + return ret class MutableModuleObjectHolder(ModuleObjectHolder, MutableInterpreterObject): - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: T.Dict[int, T.Any]) -> 'MutableModuleObjectHolder': # Deepcopy only held object, not interpreter modobj = copy.deepcopy(self.held_object, memo) return MutableModuleObjectHolder(modobj, self.interpreter) -_Target = T.TypeVar('_Target', bound=build.Target) - - -class TargetHolder(InterpreterObject, ObjectHolder[_Target]): - def __init__(self, target: _Target, interp: 'Interpreter'): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, target, interp.subproject) - self.interpreter = interp +_BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build.BothLibraries]) -_BuildTarget = T.TypeVar('_BuildTarget', bound=build.BuildTarget) - -class BuildTargetHolder(TargetHolder[_BuildTarget]): +class BuildTargetHolder(ObjectHolder[_BuildTarget]): def __init__(self, target: _BuildTarget, interp: 'Interpreter'): super().__init__(target, interp) self.methods.update({'extract_objects': self.extract_objects_method, @@ -856,62 +785,84 @@ class BuildTargetHolder(TargetHolder[_BuildTarget]): 'get_id': self.get_id_method, 'outdir': self.outdir_method, 'full_path': self.full_path_method, + 'path': self.path_method, + 'found': self.found_method, 'private_dir_include': self.private_dir_include_method, }) - def __repr__(self): + def __repr__(self) -> str: r = '<{} {}: {}>' h = self.held_object return r.format(self.__class__.__name__, h.get_id(), h.filename) - def is_cross(self): - return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine) + @property + def _target_object(self) -> build.BuildTarget: + if isinstance(self.held_object, build.BothLibraries): + return self.held_object.get_preferred_library() + assert isinstance(self.held_object, build.BuildTarget) + return self.held_object + + def is_cross(self) -> bool: + return not self._target_object.environment.machines.matches_build_machine(self._target_object.for_machine) @noPosargs - @permittedKwargs({}) - def private_dir_include_method(self, args, kwargs): - return IncludeDirsHolder(build.IncludeDirs('', [], False, - [self.interpreter.backend.get_target_private_dir(self.held_object)])) + @noKwargs + @FeatureNew('BuildTarget.found', '0.59.0') + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return True @noPosargs - @permittedKwargs({}) - def full_path_method(self, args, kwargs): - return self.interpreter.backend.get_target_filename_abs(self.held_object) + @noKwargs + def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs: + return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)]) @noPosargs - @permittedKwargs({}) - def outdir_method(self, args, kwargs): - return self.interpreter.backend.get_target_dir(self.held_object) + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_filename_abs(self._target_object) - @permittedKwargs({}) - def extract_objects_method(self, args, kwargs): - gobjs = self.held_object.extract_objects(args) - return GeneratedObjectsHolder(gobjs) + @noPosargs + @noKwargs + @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead') + def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_filename_abs(self._target_object) - @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive']) @noPosargs - @permittedKwargs({'recursive'}) - def extract_all_objects_method(self, args, kwargs): - recursive = kwargs.get('recursive', False) - gobjs = self.held_object.extract_all_objects(recursive) - if gobjs.objlist and 'recursive' not in kwargs: - mlog.warning('extract_all_objects called without setting recursive ' - 'keyword argument. Meson currently defaults to ' - 'non-recursive to maintain backward compatibility but ' - 'the default will be changed in the future.', - location=self.current_node) - return GeneratedObjectsHolder(gobjs) + @noKwargs + def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.interpreter.backend.get_target_dir(self._target_object) + + @noKwargs + @typed_pos_args('extract_objects', varargs=(mesonlib.File, str)) + def extract_objects_method(self, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects: + return self._target_object.extract_objects(args[0]) + + @noPosargs + @typed_kwargs( + 'extract_all_objects', + KwargInfo( + 'recursive', bool, default=False, since='0.46.0', + not_set_warning=textwrap.dedent('''\ + extract_all_objects called without setting recursive + keyword argument. Meson currently defaults to + non-recursive to maintain backward compatibility but + the default will be changed in the future. + ''') + ) + ) + def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: + return self._target_object.extract_all_objects(kwargs['recursive']) @noPosargs - @permittedKwargs({}) - def get_id_method(self, args, kwargs): - return self.held_object.get_id() + @noKwargs + def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._target_object.get_id() @FeatureNew('name', '0.54.0') @noPosargs - @permittedKwargs({}) - def name_method(self, args, kwargs): - return self.held_object.name + @noKwargs + def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self._target_object.name class ExecutableHolder(BuildTargetHolder[build.Executable]): pass @@ -920,37 +871,32 @@ class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]): pass class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): - def __init__(self, target: build.SharedLibrary, interp: 'Interpreter'): - super().__init__(target, interp) - # Set to True only when called from self.func_shared_lib(). - target.shared_library_only = False + pass -class BothLibrariesHolder(BuildTargetHolder): - def __init__(self, shared_holder, static_holder, interp): +class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): + def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'): # FIXME: This build target always represents the shared library, but # that should be configurable. - super().__init__(shared_holder.held_object, interp) - self.shared_holder = shared_holder - self.static_holder = static_holder + super().__init__(libs, interp) self.methods.update({'get_shared_lib': self.get_shared_lib_method, 'get_static_lib': self.get_static_lib_method, }) - def __repr__(self): + def __repr__(self) -> str: r = '<{} {}: {}, {}: {}>' - h1 = self.shared_holder.held_object - h2 = self.static_holder.held_object + h1 = self.held_object.shared + h2 = self.held_object.static return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) @noPosargs - @permittedKwargs({}) - def get_shared_lib_method(self, args, kwargs): - return self.shared_holder + @noKwargs + def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary: + return self.held_object.shared @noPosargs - @permittedKwargs({}) - def get_static_lib_method(self, args, kwargs): - return self.static_holder + @noKwargs + def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary: + return self.held_object.static class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): pass @@ -958,7 +904,7 @@ class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): class JarHolder(BuildTargetHolder[build.Jar]): pass -class CustomTargetIndexHolder(TargetHolder[build.CustomTargetIndex]): +class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]): def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): super().__init__(target, interp) self.methods.update({'full_path': self.full_path_method, @@ -966,75 +912,69 @@ class CustomTargetIndexHolder(TargetHolder[build.CustomTargetIndex]): @FeatureNew('custom_target[i].full_path', '0.54.0') @noPosargs - @permittedKwargs({}) - def full_path_method(self, args, kwargs): + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + assert self.interpreter.backend is not None return self.interpreter.backend.get_target_filename_abs(self.held_object) -class CustomTargetHolder(TargetHolder[build.CustomTarget]): +class CustomTargetHolder(ObjectHolder[build.CustomTarget]): def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'): super().__init__(target, interp) self.methods.update({'full_path': self.full_path_method, 'to_list': self.to_list_method, }) - def __repr__(self): + def __repr__(self) -> str: r = '<{} {}: {}>' h = self.held_object return r.format(self.__class__.__name__, h.get_id(), h.command) @noPosargs - @permittedKwargs({}) - def full_path_method(self, args, kwargs): + @noKwargs + def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.interpreter.backend.get_target_filename_abs(self.held_object) @FeatureNew('custom_target.to_list', '0.54.0') @noPosargs - @permittedKwargs({}) - def to_list_method(self, args, kwargs): + @noKwargs + def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]: result = [] for i in self.held_object: - result.append(CustomTargetIndexHolder(i, self.interpreter)) + result.append(i) return result - def __getitem__(self, index): - return CustomTargetIndexHolder(self.held_object[index], self.interpreter) + def __getitem__(self, index: int) -> build.CustomTargetIndex: + return self.held_object[index] - def __setitem__(self, index, value): # lgtm[py/unexpected-raise-in-special-method] + def __setitem__(self, index: int, value: T.Any) -> None: # lgtm[py/unexpected-raise-in-special-method] raise InterpreterException('Cannot set a member of a CustomTarget') - def __delitem__(self, index): # lgtm[py/unexpected-raise-in-special-method] + def __delitem__(self, index: int) -> None: # lgtm[py/unexpected-raise-in-special-method] raise InterpreterException('Cannot delete a member of a CustomTarget') - def outdir_include(self): - return IncludeDirsHolder(build.IncludeDirs('', [], False, - [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))])) - -class RunTargetHolder(TargetHolder): - def __init__(self, target, interp): - super().__init__(target, interp) - - def __repr__(self): - r = '<{} {}: {}>' - h = self.held_object - return r.format(self.__class__.__name__, h.get_id(), h.command) +class RunTargetHolder(ObjectHolder[build.RunTarget]): + pass +class AliasTargetHolder(ObjectHolder[build.AliasTarget]): + pass -class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): +class GeneratedListHolder(ObjectHolder[build.GeneratedList]): + pass - def __init__(self, gen: 'build.Generator', interpreter: 'Interpreter'): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, gen, interpreter.subproject) - self.interpreter = interpreter +class GeneratorHolder(ObjectHolder[build.Generator]): + def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): + super().__init__(gen, interpreter) self.methods.update({'process': self.process_method}) - @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder)) + @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) @typed_kwargs( 'generator.process', KwargInfo('preserve_path_from', str, since='0.45.0'), KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), ) - def process_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder]]], - kwargs: 'kwargs.GeneratorProcess') -> GeneratedListHolder: + def process_method(self, + args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]], + kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList: preserve_path_from = kwargs['preserve_path_from'] if preserve_path_from is not None: preserve_path_from = os.path.normpath(preserve_path_from) @@ -1042,12 +982,12 @@ class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): # This is a bit of a hack. Fix properly before merging. raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') - if any(isinstance(a, (CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder)) for a in args[0]): + if any(isinstance(a, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for a in args[0]): FeatureNew.single_use( f'Calling generator.process with CustomTaget or Index of CustomTarget.', '0.57.0', self.interpreter.subproject) - gl = self.held_object.process_files(mesonlib.unholder(args[0]), self.interpreter, + gl = self.held_object.process_files(args[0], self.interpreter, preserve_path_from, extra_args=kwargs['extra_args']) - return GeneratedListHolder(gl) + return gl diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 9734caa..1cc2082 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -4,15 +4,14 @@ """Keyword Argument type annotations.""" +from mesonbuild import coredata import typing as T from typing_extensions import TypedDict, Literal +from .. import build from ..mesonlib import MachineChoice, File -from .interpreterobjects import ( - BuildTargetHolder, CustomTargetHolder, EnvironmentVariablesHolder, - FeatureOptionHolder, TargetHolder -) +from .interpreterobjects import EnvironmentVariablesObject class FuncAddProjectArgs(TypedDict): @@ -34,13 +33,13 @@ class BaseTest(TypedDict): """Shared base for the Rust module.""" - args: T.List[T.Union[str, File, TargetHolder]] + args: T.List[T.Union[str, File, build.Target]] should_fail: bool timeout: int workdir: T.Optional[str] - depends: T.List[T.Union[CustomTargetHolder, BuildTargetHolder]] + depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]] priority: int - env: T.Union[EnvironmentVariablesHolder, T.List[str], T.Dict[str, str], str] + env: T.Union[EnvironmentVariablesObject, T.List[str], T.Dict[str, str], str] suite: T.List[str] @@ -70,7 +69,7 @@ class ExtractRequired(TypedDict): a boolean or a feature option should inherit it's arguments from this class. """ - required: T.Union[bool, 'FeatureOptionHolder'] + required: T.Union[bool, coredata.UserFeatureOption] class FuncGenerator(TypedDict): @@ -81,7 +80,7 @@ class FuncGenerator(TypedDict): output: T.List[str] depfile: bool capture: bool - depends: T.List[T.Union['BuildTargetHolder', 'CustomTargetHolder']] + depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] class GeneratorProcess(TypedDict): @@ -90,3 +89,16 @@ class GeneratorProcess(TypedDict): preserve_path_from: T.Optional[str] extra_args: T.List[str] + +class DependencyMethodPartialDependency(TypedDict): + + """ Keyword Arguments for the dep.partial_dependency methods """ + + compile_args: bool + link_args: bool + links: bool + includes: bool + sources: bool + +class BuildTargeMethodExtractAllObjects(TypedDict): + recursive: bool diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index e76ad2e..c3cc0d2 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -5,22 +5,24 @@ from .. import dependencies from .. import build from .. import mlog -from ..mesonlib import unholder, MachineChoice, OptionKey +from ..mesonlib import MachineChoice, OptionKey from ..programs import OverrideProgram, ExternalProgram -from ..interpreterbase import (InterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated, +from ..interpreterbase import (MesonInterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated, typed_pos_args, permittedKwargs, noArgsFlattening, noPosargs, noKwargs, MesonVersionString, InterpreterException) -from .compiler import CompilerHolder from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder, CustomTargetHolder, CustomTargetIndexHolder, - EnvironmentVariablesHolder) + EnvironmentVariablesObject) import typing as T -class MesonMain(InterpreterObject): +if T.TYPE_CHECKING: + from .interpreter import Interpreter + +class MesonMain(MesonInterpreterObject): def __init__(self, build: 'build.Build', interpreter: 'Interpreter'): - InterpreterObject.__init__(self) + super().__init__() self.build = build self.interpreter = interpreter self.methods.update({'get_compiler': self.get_compiler_method, @@ -54,11 +56,11 @@ class MesonMain(InterpreterObject): 'add_devenv': self.add_devenv_method, }) - def _find_source_script(self, prog: T.Union[str, mesonlib.File, ExecutableHolder], args): - - if isinstance(prog, (ExecutableHolder, ExternalProgramHolder)): - return self.interpreter.backend.get_executable_serialisation([unholder(prog)] + args) - found = self.interpreter.func_find_program({}, prog, {}).held_object + def _find_source_script(self, prog: T.Union[str, mesonlib.File, build.Executable, ExternalProgram], args): + + if isinstance(prog, (build.Executable, ExternalProgram)): + return self.interpreter.backend.get_executable_serialisation([prog] + args) + found = self.interpreter.func_find_program({}, prog, {}) es = self.interpreter.backend.get_executable_serialisation([found] + args) es.subproject = self.interpreter.subproject return es @@ -72,7 +74,6 @@ class MesonMain(InterpreterObject): script_args = [] # T.List[str] new = False for a in args: - a = unholder(a) if isinstance(a, str): script_args.append(a) elif isinstance(a, mesonlib.File): @@ -252,7 +253,7 @@ class MesonMain(InterpreterObject): for_machine = self.interpreter.machine_from_native_kwarg(kwargs) clist = self.interpreter.coredata.compilers[for_machine] if cname in clist: - return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject) + return clist[cname] raise InterpreterException(f'Tried to access compiler for language "{cname}", not specified for {for_machine.get_lower_case_name()} machine.') @noPosargs @@ -284,7 +285,6 @@ class MesonMain(InterpreterObject): name, exe = args if not isinstance(name, str): raise InterpreterException('First argument must be a string') - exe = unholder(exe) if isinstance(exe, mesonlib.File): abspath = exe.absolute_path(self.interpreter.environment.source_dir, self.interpreter.environment.build_dir) @@ -304,7 +304,6 @@ class MesonMain(InterpreterObject): dep = args[1] if not isinstance(name, str) or not name: raise InterpreterException('First argument must be a string and cannot be empty') - dep = unholder(dep) if not isinstance(dep, dependencies.Dependency): raise InterpreterException('Second argument must be a dependency object') identifier = dependencies.get_dep_identifier(name, kwargs) @@ -375,9 +374,9 @@ class MesonMain(InterpreterObject): @FeatureNew('add_devenv', '0.58.0') @noKwargs - @typed_pos_args('add_devenv', (str, list, dict, EnvironmentVariablesHolder)) - def add_devenv_method(self, args: T.Union[str, list, dict, EnvironmentVariablesHolder], kwargs: T.Dict[str, T.Any]) -> None: + @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: env = args[0] if isinstance(env, (str, list, dict)): - env = EnvironmentVariablesHolder(env) - self.build.devenv.append(env.held_object) + env = EnvironmentVariablesObject(env) + self.build.devenv.append(env.vars) diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index d5ef367..d776ae1 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -14,6 +14,7 @@ __all__ = [ 'InterpreterObject', + 'MesonInterpreterObject', 'ObjectHolder', 'RangeHolder', 'MesonVersionString', @@ -38,12 +39,14 @@ __all__ = [ 'noKwargs', 'stringArgs', 'noArgsFlattening', + 'unholder_return', 'disablerIfNotFound', 'permittedKwargs', 'typed_pos_args', 'ContainerTypeInfo', 'KwargInfo', 'typed_kwargs', + 'FeatureCheckBase', 'FeatureNew', 'FeatureDeprecated', 'FeatureNewKwargs', @@ -58,12 +61,14 @@ __all__ = [ 'TYPE_elementary', 'TYPE_var', 'TYPE_nvar', + 'TYPE_kwargs', 'TYPE_nkwargs', 'TYPE_key_resolver', ] from .baseobjects import ( InterpreterObject, + MesonInterpreterObject, ObjectHolder, RangeHolder, MutableInterpreterObject, @@ -75,6 +80,7 @@ from .baseobjects import ( TYPE_elementary, TYPE_var, TYPE_nvar, + TYPE_kwargs, TYPE_nkwargs, TYPE_key_resolver, ) @@ -85,12 +91,14 @@ from .decorators import ( noKwargs, stringArgs, noArgsFlattening, + unholder_return, disablerIfNotFound, permittedKwargs, typed_pos_args, ContainerTypeInfo, KwargInfo, typed_kwargs, + FeatureCheckBase, FeatureNew, FeatureDeprecated, FeatureNewKwargs, diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py new file mode 100644 index 0000000..b5663a5 --- /dev/null +++ b/mesonbuild/interpreterbase/_unholder.py @@ -0,0 +1,37 @@ +# Copyright 2013-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, TYPE_var +from .exceptions import InvalidArguments +from ..mesonlib import HoldableObject, MesonBugException + +import typing as T + +def _unholder(obj: T.Union[TYPE_var, InterpreterObject]) -> TYPE_var: + if isinstance(obj, (int, bool, str)): + return obj + elif isinstance(obj, list): + return [_unholder(x) for x in obj] + elif isinstance(obj, dict): + return {k: _unholder(v) for k, v in obj.items()} + elif isinstance(obj, ObjectHolder): + assert isinstance(obj.held_object, HoldableObject) + return obj.held_object + elif isinstance(obj, MesonInterpreterObject): + return obj + elif isinstance(obj, HoldableObject): + raise MesonBugException(f'Argument {obj} of type {type(obj).__name__} is not held by an ObjectHolder.') + elif isinstance(obj, InterpreterObject): + raise InvalidArguments(f'Argument {obj} of type {type(obj).__name__} cannot be passed to a method or function') + raise MesonBugException(f'Unknown object {obj} of type {type(obj).__name__} in the parameters.') diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index f48ab9b..d82aad2 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -15,58 +15,73 @@ from .. import mparser from .exceptions import InvalidCode from .helpers import flatten +from ..mesonlib import HoldableObject import typing as T -TV_fw_var = T.Union[str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder'] +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, float, bool] -TYPE_var = T.Union[TYPE_elementary, T.List[T.Any], T.Dict[str, T.Any], 'InterpreterObject', 'ObjectHolder'] +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) -> None: - self.methods = {} # type: T.Dict[str, T.Callable[[T.List[TYPE_nvar], TYPE_nkwargs], TYPE_var]] + def __init__(self, *, subproject: T.Optional[str] = None) -> None: + 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 = None # type: mparser.BaseNode + self.current_node: mparser.BaseNode = None + self.subproject: str = subproject or '' 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] if not getattr(method, 'no-args-flattening', False): args = flatten(args) return method(args, kwargs) - raise InvalidCode('Unknown method "%s" in object.' % method_name) + raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.') + +class MesonInterpreterObject(InterpreterObject): + ''' All non-elementary objects and non-object-holders should be derived from this ''' -class MutableInterpreterObject(InterpreterObject): - def __init__(self) -> None: - super().__init__() +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(T.Generic[TV_InterpreterObject]): - def __init__(self, obj: TV_InterpreterObject, subproject: str = '') -> None: +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.subproject = subproject + self.interpreter = interpreter + self.env = self.interpreter.environment 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(InterpreterObject): - def __init__(self, start: int, stop: int, step: int) -> None: - super().__init__() +class RangeHolder(MesonInterpreterObject): + def __init__(self, start: int, stop: int, step: int, *, subproject: T.Optional[str] = None) -> None: + super().__init__(subproject=subproject) self.range = range(start, stop, step) def __iter__(self) -> T.Iterator[int]: diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index b82a413..a011b66 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -13,10 +13,11 @@ # limitations under the License. from .. import mesonlib, mlog -from .baseobjects import TV_func, TYPE_nvar +from .baseobjects import TV_func, TYPE_var from .disabler import Disabler from .exceptions import InterpreterException, InvalidArguments from .helpers import check_stringlist, get_callee_args +from ._unholder import _unholder from functools import wraps import abc @@ -67,13 +68,20 @@ def noArgsFlattening(f: TV_func) -> TV_func: setattr(f, 'no-args-flattening', True) # noqa: B010 return f +def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]: + @wraps(f) + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: + res = f(*wrapped_args, **wrapped_kwargs) + return _unholder(res) + return T.cast(T.Callable[..., TYPE_var], wrapped) + def disablerIfNotFound(f: TV_func) -> TV_func: @wraps(f) def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: kwargs = get_callee_args(wrapped_args)[3] disabler = kwargs.pop('disabler', False) ret = f(*wrapped_args, **wrapped_kwargs) - if disabler and not ret.held_object.found(): + if disabler and not ret.found(): return Disabler() return ret return T.cast(TV_func, wrapped) @@ -292,8 +300,10 @@ class KwargInfo(T.Generic[_T]): validation, just converstion. :param deprecated_values: a dictionary mapping a value to the version of meson it was deprecated in. - :param since: a dictionary mapping a value to the version of meson it was + :param since_values: a dictionary mapping a value to the version of meson it was added in. + :param not_set_warning: A warning messsage that is logged if the kwarg is not + set by the user. """ def __init__(self, name: str, types: T.Union[T.Type[_T], T.Tuple[T.Type[_T], ...], ContainerTypeInfo], @@ -304,7 +314,8 @@ class KwargInfo(T.Generic[_T]): deprecated: T.Optional[str] = None, deprecated_values: T.Optional[T.Dict[str, str]] = None, validator: T.Optional[T.Callable[[_T], T.Optional[str]]] = None, - convertor: T.Optional[T.Callable[[_T], TYPE_nvar]] = None): + convertor: T.Optional[T.Callable[[_T], TYPE_var]] = None, + not_set_warning: T.Optional[str] = None): self.name = name self.types = types self.required = required @@ -316,6 +327,7 @@ class KwargInfo(T.Generic[_T]): self.deprecated_values = deprecated_values self.validator = validator self.convertor = convertor + self.not_set_warning = not_set_warning def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]: @@ -410,6 +422,8 @@ def typed_kwargs(name: str, *types: KwargInfo) -> T.Callable[..., T.Any]: kwargs[info.name] = info.types.container(info.default) else: kwargs[info.name] = info.default + if info.not_set_warning: + mlog.warning(info.not_set_warning) if info.convertor: kwargs[info.name] = info.convertor(kwargs[info.name]) diff --git a/mesonbuild/interpreterbase/disabler.py b/mesonbuild/interpreterbase/disabler.py index 50bc5bb..81f5264 100644 --- a/mesonbuild/interpreterbase/disabler.py +++ b/mesonbuild/interpreterbase/disabler.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .baseobjects import InterpreterObject +from .baseobjects import MesonInterpreterObject import typing as T -class Disabler(InterpreterObject): +class Disabler(MesonInterpreterObject): def __init__(self) -> None: super().__init__() self.methods.update({'found': self.found_method}) diff --git a/mesonbuild/interpreterbase/helpers.py b/mesonbuild/interpreterbase/helpers.py index 1070a9e..2602e80 100644 --- a/mesonbuild/interpreterbase/helpers.py +++ b/mesonbuild/interpreterbase/helpers.py @@ -19,15 +19,15 @@ import collections.abc import typing as T if T.TYPE_CHECKING: - from .baseobjects import TYPE_nvar, TV_fw_args, TV_fw_kwargs + from .baseobjects import TYPE_var, TYPE_kwargs -def flatten(args: T.Union['TYPE_nvar', T.List['TYPE_nvar']]) -> T.List['TYPE_nvar']: +def flatten(args: T.Union['TYPE_var', T.List['TYPE_var']]) -> T.List['TYPE_var']: if isinstance(args, mparser.StringNode): assert isinstance(args.value, str) return [args.value] if not isinstance(args, collections.abc.Sequence): return [args] - result: T.List['TYPE_nvar'] = [] + result: T.List['TYPE_var'] = [] for a in args: if isinstance(a, list): rest = flatten(a) @@ -51,7 +51,7 @@ def default_resolve_key(key: mparser.BaseNode) -> str: raise InterpreterException('Invalid kwargs format.') return key.value -def get_callee_args(wrapped_args: T.Sequence[T.Any], want_subproject: bool = False) -> T.Tuple[T.Any, mparser.BaseNode, 'TV_fw_args', 'TV_fw_kwargs', T.Optional[str]]: +def get_callee_args(wrapped_args: T.Sequence[T.Any], want_subproject: bool = False) -> T.Tuple[T.Any, mparser.BaseNode, T.List['TYPE_var'], 'TYPE_kwargs', T.Optional[str]]: s = wrapped_args[0] n = len(wrapped_args) # Raise an error if the codepaths are not there diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 1e4be01..0acb699 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -20,13 +20,15 @@ from .. import environment, dependencies from .baseobjects import ( InterpreterObject, + MesonInterpreterObject, MutableInterpreterObject, + InterpreterObjectTypeVar, ObjectHolder, RangeHolder, + TYPE_elementary, TYPE_var, - TYPE_nvar, - TYPE_nkwargs, + TYPE_kwargs, ) from .exceptions import ( @@ -41,25 +43,43 @@ from .exceptions import ( from .decorators import FeatureNew, builtinMethodNoKwargs from .disabler import Disabler, is_disabled from .helpers import check_stringlist, default_resolve_key, flatten +from ._unholder import _unholder import os, copy, re import typing as T +if T.TYPE_CHECKING: + from ..interpreter import Interpreter + +HolderMapType = T.Dict[ + T.Type[mesonlib.HoldableObject], + # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar] + T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]] +] + +FunctionType = T.Dict[ + str, + T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var] +] class MesonVersionString(str): pass class InterpreterBase: - elementary_types = (int, float, str, bool, list) + elementary_types = (int, str, bool, list) def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root - self.funcs = {} # type: T.Dict[str, T.Callable[[mparser.BaseNode, T.List[TYPE_nvar], T.Dict[str, TYPE_nvar]], TYPE_var]] - self.builtin = {} # type: T.Dict[str, InterpreterObject] + self.funcs: FunctionType = {} + self.builtin: T.Dict[str, InterpreterObject] = {} + # Holder maps store a mapping from an HoldableObject to a class ObjectHolder + self.holder_map: HolderMapType = {} + self.bound_holder_map: HolderMapType = {} self.subdir = subdir self.root_subdir = subdir self.subproject = subproject - self.variables = {} # type: T.Dict[str, TYPE_var] + # TODO: This should actually be more strict: T.Union[TYPE_elementary, InterpreterObject] + self.variables: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} self.argument_depth = 0 self.current_lineno = -1 # Current node set during a function call. This can be used as location @@ -137,7 +157,7 @@ class InterpreterBase: raise e i += 1 # In THE FUTURE jump over blocks and stuff. - def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[TYPE_var]: + def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: self.current_node = cur if isinstance(cur, mparser.FunctionNode): return self.function_call(cur) @@ -191,14 +211,14 @@ class InterpreterBase: raise InvalidCode("Unknown statement.") return None - def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> list: + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> T.List[T.Union[TYPE_var, InterpreterObject]]: (arguments, kwargs) = self.reduce_arguments(cur.args) if len(kwargs) > 0: raise InvalidCode('Keyword arguments are invalid in array construction.') return arguments @FeatureNew('dict', '0.47.0') - def evaluate_dictstatement(self, cur: mparser.DictNode) -> TYPE_nkwargs: + def evaluate_dictstatement(self, cur: mparser.DictNode) -> T.Union[TYPE_var, InterpreterObject]: def resolve_key(key: mparser.BaseNode) -> str: if not isinstance(key, mparser.StringNode): FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject) @@ -387,7 +407,7 @@ The result of this is undefined and will become a hard error in a future Meson r else: raise InvalidCode('You broke me.') - def evaluate_ternary(self, node: mparser.TernaryNode) -> TYPE_var: + def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Union[TYPE_var, InterpreterObject]: assert(isinstance(node, mparser.TernaryNode)) result = self.evaluate_statement(node.condition) if isinstance(result, Disabler): @@ -479,7 +499,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints') self.set_variable(varname, new_value) - def evaluate_indexing(self, node: mparser.IndexNode) -> TYPE_var: + def evaluate_indexing(self, node: mparser.IndexNode) -> T.Union[TYPE_elementary, InterpreterObject]: assert(isinstance(node, mparser.IndexNode)) iobject = self.evaluate_statement(node.iobject) if isinstance(iobject, Disabler): @@ -494,7 +514,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Key is not a string') try: # The cast is required because we don't have recursive types... - return T.cast(TYPE_var, iobject[index]) + return T.cast(T.Union[TYPE_elementary, InterpreterObject], iobject[index]) except KeyError: raise InterpreterException('Key %s is not in dict' % index) else: @@ -504,35 +524,45 @@ The result of this is undefined and will become a hard error in a future Meson r # Ignore the MyPy error, since we don't know all indexable types here # and we handle non indexable types with an exception # TODO maybe find a better solution - return iobject[index] # type: ignore + res = iobject[index] # type: ignore + # Only holderify if we are dealing with `InterpreterObject`, since raw + # lists already store ObjectHolders + if isinstance(iobject, InterpreterObject): + return self._holderify(res) + else: + return res except IndexError: # We are already checking for the existence of __getitem__, so this should be save raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) # type: ignore - def function_call(self, node: mparser.FunctionNode) -> T.Optional[TYPE_var]: + def function_call(self, node: mparser.FunctionNode) -> T.Optional[T.Union[TYPE_elementary, InterpreterObject]]: func_name = node.func_name - (posargs, kwargs) = self.reduce_arguments(node.args) + (h_posargs, h_kwargs) = self.reduce_arguments(node.args) + (posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs) if is_disabled(posargs, kwargs) and func_name not in {'get_variable', 'set_variable', 'is_disabler'}: return Disabler() if func_name in self.funcs: func = self.funcs[func_name] - func_args = posargs # type: T.Any + func_args = posargs if not getattr(func, 'no-args-flattening', False): func_args = flatten(posargs) - return func(node, func_args, kwargs) + res = func(node, func_args, kwargs) + return self._holderify(res) else: self.unknown_function_called(func_name) return None - def method_call(self, node: mparser.MethodNode) -> TYPE_var: + def method_call(self, node: mparser.MethodNode) -> T.Optional[T.Union[TYPE_var, InterpreterObject]]: invokable = node.source_object + obj: T.Union[TYPE_var, InterpreterObject] if isinstance(invokable, mparser.IdNode): object_name = invokable.value obj = self.get_variable(object_name) else: obj = self.evaluate_statement(invokable) method_name = node.name - (args, kwargs) = self.reduce_arguments(node.args) + (h_args, h_kwargs) = self.reduce_arguments(node.args) + (args, kwargs) = self._unholder_args(h_args, h_kwargs) if is_disabled(args, kwargs): return Disabler() if isinstance(obj, str): @@ -545,8 +575,6 @@ The result of this is undefined and will become a hard error in a future Meson r return self.array_method_call(obj, method_name, args, kwargs) if isinstance(obj, dict): return self.dict_method_call(obj, method_name, args, kwargs) - if isinstance(obj, mesonlib.File): - raise InvalidArguments('File object "%s" is not callable.' % obj) if not isinstance(obj, InterpreterObject): raise InvalidArguments('Variable "%s" is not callable.' % object_name) # Special case. This is the only thing you can do with a disabler @@ -556,15 +584,48 @@ The result of this is undefined and will become a hard error in a future Meson r return False else: return Disabler() + # TODO: InterpreterBase **really** shouldn't be in charge of checking this if method_name == 'extract_objects': if not isinstance(obj, ObjectHolder): - raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}"') + raise InvalidArguments(f'Invalid operation "extract_objects" on variable "{object_name}" of type {type(obj).__name__}') self.validate_extraction(obj.held_object) obj.current_node = node - return obj.method_call(method_name, args, kwargs) + return self._holderify(obj.method_call(method_name, args, kwargs)) + + def _holderify(self, res: T.Optional[TYPE_var]) -> T.Union[TYPE_elementary, InterpreterObject]: + if res is None: + return None + if isinstance(res, (int, bool, str)): + return res + elif isinstance(res, list): + return [self._holderify(x) for x in res] + elif isinstance(res, dict): + return {k: self._holderify(v) for k, v in res.items()} + elif isinstance(res, mesonlib.HoldableObject): + # Always check for an exact match first. + cls = self.holder_map.get(type(res), None) + if cls is not None: + # Casts to Interpreter are required here since an assertion would + # not work for the `ast` module. + return cls(res, T.cast('Interpreter', self)) + # Try the boundary types next. + for typ, cls in self.bound_holder_map.items(): + if isinstance(res, typ): + return cls(res, T.cast('Interpreter', self)) + raise mesonlib.MesonBugException(f'Object {res} of type {type(res).__name__} is neither in self.holder_map nor self.bound_holder_map.') + elif isinstance(res, ObjectHolder): + raise mesonlib.MesonBugException(f'Returned object {res} of type {type(res).__name__} is an object holder.') + elif isinstance(res, MesonInterpreterObject): + return res + raise mesonlib.MesonBugException(f'Unknown returned object {res} of type {type(res).__name__} in the parameters.') + + def _unholder_args(self, + args: T.List[T.Union[TYPE_var, InterpreterObject]], + kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: + return [_unholder(x) for x in args], {k: _unholder(v) for k, v in kwargs.items()} @builtinMethodNoKwargs - def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int]: + def bool_method_call(self, obj: bool, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int]: if method_name == 'to_string': if not posargs: if obj: @@ -587,7 +648,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) @builtinMethodNoKwargs - def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, bool]: + def int_method_call(self, obj: int, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, bool]: if method_name == 'is_even': if not posargs: return obj % 2 == 0 @@ -607,7 +668,7 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException('Unknown method "%s" for an integer.' % method_name) @staticmethod - def _get_one_string_posarg(posargs: T.List[TYPE_nvar], method_name: str) -> str: + def _get_one_string_posarg(posargs: T.List[TYPE_var], method_name: str) -> str: if len(posargs) > 1: m = '{}() must have zero or one arguments' raise InterpreterException(m.format(method_name)) @@ -620,7 +681,7 @@ The result of this is undefined and will become a hard error in a future Meson r return None @builtinMethodNoKwargs - def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> T.Union[str, int, bool, T.List[str]]: + def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool, T.List[str]]: if method_name == 'strip': s1 = self._get_one_string_posarg(posargs, 'strip') if s1 is not None: @@ -692,7 +753,7 @@ The result of this is undefined and will become a hard error in a future Meson r return obj.replace(posargs[0], posargs[1]) raise InterpreterException('Unknown method "%s" for a string.' % method_name) - def format_string(self, templ: str, args: T.List[TYPE_nvar]) -> str: + def format_string(self, templ: str, args: T.List[TYPE_var]) -> str: arg_strings = [] for arg in args: if isinstance(arg, mparser.BaseNode): @@ -713,7 +774,11 @@ The result of this is undefined and will become a hard error in a future Meson r raise InvalidCode('Unknown function "%s".' % func_name) @builtinMethodNoKwargs - def array_method_call(self, obj: T.List[TYPE_var], method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: + def array_method_call(self, + obj: T.List[T.Union[TYPE_elementary, InterpreterObject]], + method_name: str, + posargs: T.List[TYPE_var], + kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if method_name == 'contains': def check_contains(el: list) -> bool: if len(posargs) != 1: @@ -754,7 +819,11 @@ The result of this is undefined and will become a hard error in a future Meson r raise InterpreterException(m.format(method_name)) @builtinMethodNoKwargs - def dict_method_call(self, obj: T.Dict[str, TYPE_var], method_name: str, posargs: T.List[TYPE_nvar], kwargs: T.Dict[str, T.Any]) -> TYPE_var: + def dict_method_call(self, + obj: T.Dict[str, T.Union[TYPE_elementary, InterpreterObject]], + method_name: str, + posargs: T.List[TYPE_var], + kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: if method_name in ('has_key', 'get'): if method_name == 'has_key': if len(posargs) != 1: @@ -795,18 +864,20 @@ The result of this is undefined and will become a hard error in a future Meson r args: mparser.ArgumentNode, key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, duplicate_key_error: T.Optional[str] = None, - ) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]: + ) -> T.Tuple[ + T.List[T.Union[TYPE_var, InterpreterObject]], + T.Dict[str, T.Union[TYPE_var, InterpreterObject]] + ]: assert(isinstance(args, mparser.ArgumentNode)) if args.incorrect_order(): raise InvalidArguments('All keyword arguments must be after positional arguments.') self.argument_depth += 1 - reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] # type: T.List[TYPE_nvar] - reduced_kw = {} # type: TYPE_nkwargs + reduced_pos: T.List[T.Union[TYPE_var, InterpreterObject]] = [self.evaluate_statement(arg) for arg in args.arguments] + reduced_kw: T.Dict[str, T.Union[TYPE_var, InterpreterObject]] = {} for key, val in args.kwargs.items(): reduced_key = key_resolver(key) - reduced_val = val # type: TYPE_nvar - if isinstance(reduced_val, mparser.BaseNode): - reduced_val = self.evaluate_statement(reduced_val) + assert isinstance(val, mparser.BaseNode) + reduced_val = self.evaluate_statement(val) if duplicate_key_error and reduced_key in reduced_kw: raise InvalidArguments(duplicate_key_error.format(reduced_key)) reduced_kw[reduced_key] = reduced_val @@ -814,7 +885,7 @@ The result of this is undefined and will become a hard error in a future Meson r final_kw = self.expand_default_kwargs(reduced_kw) return reduced_pos, final_kw - def expand_default_kwargs(self, kwargs: TYPE_nkwargs) -> TYPE_nkwargs: + def expand_default_kwargs(self, kwargs: T.Dict[str, T.Union[TYPE_var, InterpreterObject]]) -> T.Dict[str, T.Union[TYPE_var, InterpreterObject]]: if 'kwargs' not in kwargs: return kwargs to_expand = kwargs.pop('kwargs') @@ -845,7 +916,7 @@ To specify a keyword argument, use : instead of =.''') self.set_variable(var_name, value) return None - def set_variable(self, varname: str, variable: TYPE_var) -> None: + def set_variable(self, varname: str, variable: T.Union[TYPE_var, InterpreterObject]) -> None: if variable is None: raise InvalidCode('Can not assign None to variable.') if not isinstance(varname, str): @@ -858,7 +929,7 @@ To specify a keyword argument, use : instead of =.''') raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) self.variables[varname] = variable - def get_variable(self, varname: str) -> TYPE_var: + def get_variable(self, varname: str) -> T.Union[TYPE_var, InterpreterObject]: if varname in self.builtin: return self.builtin[varname] if varname in self.variables: @@ -869,5 +940,5 @@ To specify a keyword argument, use : instead of =.''') return isinstance(value, (InterpreterObject, dependencies.Dependency, str, int, list, dict, mesonlib.File)) - def validate_extraction(self, buildtarget: InterpreterObject) -> None: + def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None: raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') diff --git a/mesonbuild/mesonlib/universal.py b/mesonbuild/mesonlib/universal.py index 08e8b9e..3714ecd 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,12 +47,14 @@ __all__ = [ 'an_unpicklable_object', 'python_command', 'project_meson_versions', + 'HoldableObject', 'File', 'FileMode', 'GitException', 'LibType', 'MachineChoice', 'MesonException', + 'MesonBugException', 'EnvironmentException', 'FileOrString', 'GitException', @@ -128,7 +131,6 @@ __all__ = [ 'substitute_values', 'substring_is_in_list', 'typeslistify', - 'unholder', 'verbose_git', 'version_compare', 'version_compare_condition_with_min', @@ -164,6 +166,14 @@ class MesonException(Exception): self.colno = colno +class MesonBugException(MesonException): + '''Exceptions thrown when there is a clear Meson bug that should be reported''' + + def __init__(self, msg: str, file: T.Optional[str] = None, + lineno: T.Optional[int] = None, colno: T.Optional[int] = None): + super().__init__(msg + '\n\n This is a Meson bug and should be reported!', + file=file, lineno=lineno, colno=colno) + class EnvironmentException(MesonException): '''Exceptions thrown while processing and creating the build environment''' @@ -258,6 +268,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). @@ -357,7 +371,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) @@ -1231,26 +1245,6 @@ def replace_if_different(dst: str, dst_tmp: str) -> None: os.unlink(dst_tmp) -@T.overload -def unholder(item: 'ObjectHolder[_T]') -> _T: ... - -@T.overload -def unholder(item: T.List['ObjectHolder[_T]']) -> T.List[_T]: ... - -@T.overload -def unholder(item: T.List[_T]) -> T.List[_T]: ... - -@T.overload -def unholder(item: T.List[T.Union[_T, 'ObjectHolder[_T]']]) -> T.List[_T]: ... - -def unholder(item): # type: ignore # TODO fix overload (somehow) - """Get the held item of an object holder or list of object holders.""" - if isinstance(item, list): - return [i.held_object if hasattr(i, 'held_object') else i for i in item] - if hasattr(item, 'held_object'): - return item.held_object - return item - def listify(item: T.Any, flatten: bool = True) -> T.List[T.Any]: ''' diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 69bb552..2b53de5 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,13 +18,12 @@ import os from .. import build -from ..mesonlib import unholder, relpath +from ..mesonlib import relpath, HoldableObject import typing as T if T.TYPE_CHECKING: from ..interpreter import Interpreter - from ..interpreter.interpreterobjects import IncludeDirsHolder, ExternalProgramHolder - from ..interpreterbase import TYPE_var, TYPE_nvar, TYPE_nkwargs + from ..interpreterbase import TYPE_var, TYPE_kwargs from ..programs import ExternalProgram class ModuleState: @@ -61,7 +60,7 @@ class ModuleState: self.target_machine = interpreter.builtin['target_machine'].held_object self.current_node = interpreter.current_node - def get_include_args(self, include_dirs: T.Iterable[T.Union[str, 'IncludeDirsHolder']], prefix: str = '-I') -> T.List[str]: + def get_include_args(self, include_dirs: T.Iterable[T.Union[str, build.IncludeDirs]], prefix: str = '-I') -> T.List[str]: if not include_dirs: return [] @@ -69,7 +68,7 @@ class ModuleState: builddir = self.environment.get_build_dir() dirs_str: T.List[str] = [] - for dirs in unholder(include_dirs): + for dirs in include_dirs: if isinstance(dirs, str): dirs_str += [f'{prefix}{dirs}'] continue @@ -89,14 +88,17 @@ class ModuleState: def find_program(self, prog: T.Union[str, T.List[str]], required: bool = True, version_func: T.Optional[T.Callable[['ExternalProgram'], str]] = None, - wanted: T.Optional[str] = None) -> 'ExternalProgramHolder': + wanted: T.Optional[str] = None) -> 'ExternalProgram': return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted) -class ModuleObject: +class ModuleObject(HoldableObject): """Base class for all objects returned by modules """ def __init__(self) -> None: - self.methods = {} # type: T.Dict[str, T.Callable[[ModuleState, T.List[TYPE_nvar], TYPE_nkwargs], T.Union[ModuleReturnValue, TYPE_var]]] + self.methods: T.Dict[ + str, + T.Callable[[ModuleState, T.List[TYPE_var], TYPE_kwargs], T.Union[ModuleReturnValue, TYPE_var]] + ] = {} class MutableModuleObject(ModuleObject): pass diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index fac3b24..cb37edc 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -18,9 +18,9 @@ import typing as T from . import ExtensionModule, ModuleReturnValue, ModuleObject -from .. import build, mesonlib, mlog +from .. import build, mesonlib, mlog, dependencies from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args -from ..interpreter import ConfigurationDataHolder, SubprojectHolder, DependencyHolder +from ..interpreter import ConfigurationDataObject, SubprojectHolder from ..interpreterbase import ( FeatureNew, FeatureNewKwargs, @@ -32,6 +32,7 @@ from ..interpreterbase import ( noKwargs, InvalidArguments, + InterpreterException, ) from ..programs import ExternalProgram @@ -109,11 +110,11 @@ class CMakeSubproject(ModuleObject): def dependency(self, state, args, kwargs): info = self._args_to_info(args) orig = self.get_variable(state, [info['dep']], {}) - assert isinstance(orig, DependencyHolder) - actual = orig.include_type_method([], {}) + assert isinstance(orig, dependencies.Dependency) + actual = orig.include_type if 'include_type' in kwargs and kwargs['include_type'] != actual: mlog.debug('Current include type is {}. Converting to requested {}'.format(actual, kwargs['include_type'])) - return orig.as_system_method([kwargs['include_type']], {}) + return orig.generate_system_dependency(kwargs['include_type']) return orig @noKwargs @@ -351,7 +352,7 @@ class CmakeModule(ExtensionModule): if 'configuration' not in kwargs: raise mesonlib.MesonException('"configuration" not specified.') conf = kwargs['configuration'] - if not isinstance(conf, ConfigurationDataHolder): + if not isinstance(conf, ConfigurationDataObject): raise mesonlib.MesonException('Argument "configuration" is not of type configuration_data') prefix = state.environment.coredata.get_option(mesonlib.OptionKey('prefix')) @@ -365,7 +366,7 @@ class CmakeModule(ExtensionModule): extra = PACKAGE_INIT_EXT.replace('@absInstallDir@', abs_install_dir) extra = extra.replace('@installPrefix@', prefix) - self.create_package_file(ifile_abs, ofile_abs, PACKAGE_RELATIVE_PATH, extra, conf.held_object) + self.create_package_file(ifile_abs, ofile_abs, PACKAGE_RELATIVE_PATH, extra, conf.conf_data) conf.mark_used() conffile = os.path.normpath(inputfile.relative_name()) @@ -389,7 +390,7 @@ class CmakeModule(ExtensionModule): raise InterpreterException('"options" cannot be used together with "cmake_options"') dirname = args[0] subp = self.interpreter.do_subproject(dirname, 'cmake', kwargs) - if not subp.held_object: + if not subp.found(): return subp return CMakeSubproject(subp, dirname) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 0a72ad6..0ae1c70 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -30,7 +30,7 @@ from . import ExtensionModule from . import ModuleReturnValue from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, extract_as_list, - join_args, unholder, + join_args, HoldableObject ) from ..dependencies import Dependency, PkgConfigDependency, InternalDependency from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs @@ -39,6 +39,7 @@ from ..build import CustomTarget, CustomTargetIndex, GeneratedList if T.TYPE_CHECKING: from ..compilers import Compiler + from ..interpreter import Interpreter # gresource compilation is broken due to the way # the resource compiler and Ninja clash about it @@ -103,14 +104,13 @@ class GnomeModule(ExtensionModule): def _get_dep(self, state, depname, native=False, required=True): kwargs = {'native': native, 'required': required} - holder = self.interpreter.func_dependency(state.current_node, [depname], kwargs) - return holder.held_object + return self.interpreter.func_dependency(state.current_node, [depname], kwargs) def _get_native_binary(self, state, name, depname, varname, required=True): # Look in overrides in case glib/gtk/etc are built as subproject prog = self.interpreter.program_from_overrides([name], []) if prog is not None: - return unholder(prog) + return prog # Look in machine file prog = state.environment.lookup_binary_entry(MachineChoice.HOST, name) @@ -125,7 +125,7 @@ class GnomeModule(ExtensionModule): return ExternalProgram(name, value) # Normal program lookup - return unholder(state.find_program(name, required=required)) + return state.find_program(name, required=required) @permittedKwargs({'glib_compile_schemas', 'gio_querymodules', 'gtk_update_icon_cache'}) @noPosargs @@ -179,7 +179,7 @@ class GnomeModule(ExtensionModule): # Validate dependencies subdirs = [] depends = [] - for (ii, dep) in enumerate(unholder(dependencies)): + for (ii, dep) in enumerate(dependencies): if isinstance(dep, mesonlib.File): subdirs.append(dep.subdir) elif isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)): @@ -323,7 +323,7 @@ class GnomeModule(ExtensionModule): subdirs = [] for resfile in dep_files[:]: resbasename = os.path.basename(resfile) - for dep in unholder(dependencies): + for dep in dependencies: if isinstance(dep, mesonlib.File): if dep.fname != resbasename: continue @@ -399,7 +399,7 @@ class GnomeModule(ExtensionModule): # require two args in order, such as -framework AVFoundation external_ldflags_nodedup = [] gi_includes = OrderedSet() - deps = mesonlib.unholder(mesonlib.listify(deps)) + deps = mesonlib.listify(deps) for dep in deps: if isinstance(dep, Dependency): @@ -409,7 +409,7 @@ class GnomeModule(ExtensionModule): if isinstance(dep, InternalDependency): cflags.update(dep.get_compile_args()) cflags.update(state.get_include_args(dep.include_directories)) - for lib in unholder(dep.libraries): + for lib in dep.libraries: if isinstance(lib, build.SharedLibrary): internal_ldflags.update(self._get_link_args(state, lib, depends, include_rpath)) libdepflags = self._get_dependencies_flags(lib.get_external_deps(), state, depends, include_rpath, @@ -426,7 +426,7 @@ class GnomeModule(ExtensionModule): external_ldflags.update(extdepflags[2]) external_ldflags_nodedup += extdepflags[3] gi_includes.update(extdepflags[4]) - for source in unholder(dep.sources): + for source in dep.sources: if isinstance(source, GirTarget): gi_includes.update([os.path.join(state.environment.get_build_dir(), source.get_subdir())]) @@ -480,9 +480,6 @@ class GnomeModule(ExtensionModule): return cflags, internal_ldflags, external_ldflags, external_ldflags_nodedup, gi_includes def _unwrap_gir_target(self, girtarget, state): - while hasattr(girtarget, 'held_object'): - girtarget = girtarget.held_object - if not isinstance(girtarget, (build.Executable, build.SharedLibrary, build.StaticLibrary)): raise MesonException('Gir target must be an executable or library') @@ -512,8 +509,6 @@ class GnomeModule(ExtensionModule): @functools.lru_cache(maxsize=None) def _gir_has_option(self, option) -> bool: exe = self.giscanner - if hasattr(exe, 'held_object'): - exe = exe.held_object if isinstance(exe, OverrideProgram): # Handle overridden g-ir-scanner assert option in ['--extra-library', '--sources-top-dirs'] @@ -539,7 +534,7 @@ class GnomeModule(ExtensionModule): link_with = mesonlib.extract_as_list(kwargs, 'link_with', pop = True) for link in link_with: - ret += self._get_link_args(state, link.held_object, depends, + ret += self._get_link_args(state, link, depends, use_gir_args=True) return ret @@ -549,7 +544,7 @@ class GnomeModule(ExtensionModule): if 'includes' in kwargs: includes = mesonlib.extract_as_list(kwargs, 'includes', pop = True) - for inc in unholder(includes): + for inc in includes: if isinstance(inc, str): ret += [f'--include={inc}'] elif isinstance(inc, GirTarget): @@ -605,7 +600,7 @@ class GnomeModule(ExtensionModule): def _scan_inc_dirs(self, kwargs): ret = mesonlib.extract_as_list(kwargs, 'include_directories', pop = True) for incd in ret: - if not isinstance(incd.held_object, (str, build.IncludeDirs)): + if not isinstance(incd, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') return ret @@ -708,7 +703,7 @@ class GnomeModule(ExtensionModule): gir_filelist_filename = os.path.join(gir_filelist_dir, f'{ns}_{nsversion}_gir_filelist') with open(gir_filelist_filename, 'w', encoding='utf-8') as gir_filelist: - for s in unholder(libsources): + for s in libsources: if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)): for custom_output in s.get_outputs(): gir_filelist.write(os.path.join(state.environment.get_build_dir(), @@ -763,11 +758,11 @@ class GnomeModule(ExtensionModule): # dependencies and also find the include directories needed for the # typelib generation custom target below. typelib_includes = [] - for dep in unholder(deps): + for dep in deps: # Add a dependency on each GirTarget listed in dependencies and add # the directory where it will be generated to the typelib includes if isinstance(dep, InternalDependency): - for source in unholder(dep.sources): + for source in dep.sources: if isinstance(source, GirTarget) and source not in depends: depends.append(source) subdir = os.path.join(state.environment.get_build_dir(), @@ -846,7 +841,7 @@ class GnomeModule(ExtensionModule): langs_compilers = self._get_girtargets_langs_compilers(girtargets) cflags, internal_ldflags, external_ldflags = self._get_langs_compilers_flags(state, langs_compilers) deps = self._get_gir_targets_deps(girtargets) - deps += mesonlib.unholder(extract_as_list(kwargs, 'dependencies', pop=True)) + deps += extract_as_list(kwargs, 'dependencies', pop=True) deps += [gir_dep] typelib_includes = self._gather_typelib_includes_and_update_depends(state, deps, depends) # ldflags will be misinterpreted by gir scanner (showing @@ -898,7 +893,7 @@ class GnomeModule(ExtensionModule): if fatal_warnings: scan_command.append('--warn-error') - generated_files = [unholder(f) for f in libsources if isinstance(unholder(f), (GeneratedList, CustomTarget, CustomTargetIndex))] + generated_files = [f for f in libsources if isinstance(f, (GeneratedList, CustomTarget, CustomTargetIndex))] scan_target = self._make_gir_target(state, girfile, scan_command, generated_files, depends, kwargs) @@ -1044,8 +1039,7 @@ class GnomeModule(ExtensionModule): src_dirs = mesonlib.extract_as_list(kwargs, 'src_dir') header_dirs = [] for src_dir in src_dirs: - if hasattr(src_dir, 'held_object'): - src_dir = src_dir.held_object + if isinstance(src_dir, HoldableObject): if not isinstance(src_dir, build.IncludeDirs): raise MesonException('Invalid keyword argument for src_dir.') for inc_dir in src_dir.get_incdirs(): @@ -1068,7 +1062,7 @@ class GnomeModule(ExtensionModule): for tool in ['scan', 'scangobj', 'mkdb', 'mkhtml', 'fixxref']: program_name = 'gtkdoc-' + tool program = state.find_program(program_name) - path = program.held_object.get_path() + path = program.get_path() args.append(f'--{program_name}={path}') if namespace: args.append('--namespace=' + namespace) @@ -1082,7 +1076,7 @@ class GnomeModule(ExtensionModule): depends = [] content_files = [] - for s in unholder(mesonlib.extract_as_list(kwargs, 'content_files')): + for s in mesonlib.extract_as_list(kwargs, 'content_files'): if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)): depends.append(s) for o in s.get_outputs(): @@ -1134,14 +1128,14 @@ class GnomeModule(ExtensionModule): def _get_build_args(self, kwargs, state, depends): args = [] - deps = mesonlib.unholder(extract_as_list(kwargs, 'dependencies')) + deps = extract_as_list(kwargs, 'dependencies') cflags = [] cflags.extend(mesonlib.stringlistify(kwargs.pop('c_args', []))) deps_cflags, internal_ldflags, external_ldflags, gi_includes = \ self._get_dependencies_flags(deps, state, depends, include_rpath=True) inc_dirs = mesonlib.extract_as_list(kwargs, 'include_directories') for incd in inc_dirs: - if not isinstance(incd.held_object, (str, build.IncludeDirs)): + if not isinstance(incd, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') @@ -1678,7 +1672,7 @@ G_END_DECLS''' vapi_includes = [] ret = [] remaining_args = [] - for arg in unholder(arg_list): + for arg in arg_list: if isinstance(arg, InternalDependency): targets = [t for t in arg.sources if isinstance(t, VapiTarget)] for target in targets: @@ -1752,11 +1746,11 @@ G_END_DECLS''' for i in inputs: if isinstance(i, str): cmd.append(os.path.join(source_dir, i)) - elif hasattr(i, 'held_object') and isinstance(i.held_object, GirTarget): - link_with += self._get_vapi_link_with(i.held_object) + elif isinstance(i, GirTarget): + link_with += self._get_vapi_link_with(i) subdir = os.path.join(state.environment.get_build_dir(), - i.held_object.get_subdir()) - gir_file = os.path.join(subdir, i.held_object.get_outputs()[0]) + i.get_subdir()) + gir_file = os.path.join(subdir, i.get_outputs()[0]) cmd.append(gir_file) else: raise MesonException('Input must be a str or GirTarget') @@ -1791,4 +1785,10 @@ G_END_DECLS''' return ModuleReturnValue(rv, created_values) def initialize(*args, **kwargs): - return GnomeModule(*args, **kwargs) + mod = GnomeModule(*args, **kwargs) + mod.interpreter.append_holder_map(GResourceTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(GResourceHeaderTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(GirTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(TypelibTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(VapiTarget, interpreter.CustomTargetHolder) + return mod diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py index 90635e6..26026fb 100644 --- a/mesonbuild/modules/hotdoc.py +++ b/mesonbuild/modules/hotdoc.py @@ -105,7 +105,6 @@ class HotdocTargetBuilder: self.cmd.extend([option, value]) def check_extra_arg_type(self, arg, value): - value = getattr(value, 'held_object', value) if isinstance(value, list): for v in value: self.check_extra_arg_type(arg, v) @@ -188,7 +187,6 @@ class HotdocTargetBuilder: def process_dependencies(self, deps): cflags = set() for dep in mesonlib.listify(ensure_list(deps)): - dep = getattr(dep, "held_object", dep) if isinstance(dep, InternalDependency): inc_args = self.state.get_include_args(dep.include_directories) cflags.update([self.replace_dirs_in_string(x) @@ -232,7 +230,6 @@ class HotdocTargetBuilder: def flatten_config_command(self): cmd = [] for arg in mesonlib.listify(self.cmd, flatten=True): - arg = getattr(arg, 'held_object', arg) if isinstance(arg, mesonlib.File): arg = arg.absolute_path(self.state.environment.get_source_dir(), self.state.environment.get_build_dir()) @@ -371,7 +368,7 @@ class HotdocTargetHolder(CustomTargetHolder): def config_path_method(self, *args, **kwargs): conf = self.held_object.hotdoc_conf.absolute_path(self.interpreter.environment.source_dir, self.interpreter.environment.build_dir) - return self.interpreter.holderify(conf) + return conf class HotdocTarget(build.CustomTarget): @@ -422,7 +419,7 @@ class HotDocModule(ExtensionModule): project_name = args[0] builder = HotdocTargetBuilder(project_name, state, self.hotdoc, self.interpreter, kwargs) target, install_script = builder.make_targets() - targets = [HotdocTargetHolder(target, self.interpreter)] + targets = [target] if install_script: targets.append(install_script) @@ -430,4 +427,6 @@ class HotDocModule(ExtensionModule): def initialize(interpreter): - return HotDocModule(interpreter) + mod = HotDocModule(interpreter) + mod.interpreter.append_holder_map(HotdocTarget, HotdocTargetHolder) + return mod diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 98a8b0d..f10fdbe 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -108,7 +108,8 @@ class I18nModule(ExtensionModule): kwargs['command'] = command inputfile = kwargs['input'] - if hasattr(inputfile, 'held_object'): + # I have no idea why/how this if isinstance(inputfile, mesonlib.HoldableObject) works / used to work... + if isinstance(inputfile, mesonlib.HoldableObject): ct = build.CustomTarget(kwargs['output'] + '_merge', state.subdir, state.subproject, kwargs) else: if isinstance(inputfile, list): diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 5cebd28..f42c1e6 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os, types +import os from pathlib import PurePath from .. import build @@ -22,7 +22,9 @@ from .. import mesonlib from .. import mlog from . import ModuleReturnValue from . import ExtensionModule -from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs +from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs, TYPE_var, TYPE_kwargs + +import typing as T already_warned_objs = set() @@ -75,7 +77,7 @@ class DependenciesHelper: def _process_reqs(self, reqs): '''Returns string names of requirements''' processed_reqs = [] - for obj in mesonlib.unholder(mesonlib.listify(reqs)): + for obj in mesonlib.listify(reqs): if not isinstance(obj, str): FeatureNew.single_use('pkgconfig.generate requirement from non-string object', '0.46.0', self.state.subproject) if hasattr(obj, 'generated_pc'): @@ -108,13 +110,13 @@ class DependenciesHelper: def add_cflags(self, cflags): self.cflags += mesonlib.stringlistify(cflags) - def _process_libs(self, libs, public): - libs = mesonlib.unholder(mesonlib.listify(libs)) + def _process_libs(self, libs, public: bool): + libs = mesonlib.listify(libs) + libs = [x.get_preferred_library() if isinstance(x, build.BothLibraries) else x for x in libs] processed_libs = [] processed_reqs = [] processed_cflags = [] for obj in libs: - shared_library_only = getattr(obj, 'shared_library_only', False) if hasattr(obj, 'pcdep'): pcdeps = mesonlib.listify(obj.pcdep) for d in pcdeps: @@ -136,7 +138,7 @@ class DependenciesHelper: if obj.found(): processed_libs += obj.get_link_args() processed_cflags += obj.get_compile_args() - elif isinstance(obj, build.SharedLibrary) and shared_library_only: + elif isinstance(obj, build.SharedLibrary) and obj.shared_library_only: # Do not pull dependencies for shared libraries because they are # only required for static linking. Adding private requires has # the side effect of exposing their cflags, which is the @@ -161,7 +163,7 @@ class DependenciesHelper: elif isinstance(obj, str): processed_libs.append(obj) else: - raise mesonlib.MesonException('library argument not a string, library or dependency object.') + raise mesonlib.MesonException(f'library argument of type {type(obj).__name__} not a string, library or dependency object.') return processed_libs, processed_reqs, processed_cflags @@ -330,9 +332,9 @@ class PkgConfigModule(ExtensionModule): except ValueError: return subdir.as_posix() - def generate_pkgconfig_file(self, state, deps, subdirs, name, description, - url, version, pcfile, conflicts, variables, - unescaped_variables, uninstalled=False, dataonly=False): + def _generate_pkgconfig_file(self, state, deps, subdirs, name, description, + url, version, pcfile, conflicts, variables, + unescaped_variables, uninstalled=False, dataonly=False): coredata = state.environment.get_coredata() if uninstalled: outdir = os.path.join(state.environment.build_dir, 'meson-uninstalled') @@ -452,6 +454,19 @@ class PkgConfigModule(ExtensionModule): if cflags and not dataonly: ofile.write('Cflags: {}\n'.format(' '.join(cflags))) + + @staticmethod + def _handle_both_libraries(args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]: + def _do_extract(arg: TYPE_var) -> TYPE_var: + if isinstance(arg, list): + return [_do_extract(x) for x in arg] + elif isinstance(arg, dict): + return {k: _do_extract(v) for k, v in arg.items()} + elif isinstance(arg, build.BothLibraries): + return arg.get_preferred_library() + return arg + return [_do_extract(x) for x in args], {k: _do_extract(v) for k, v in kwargs.items()} + @FeatureNewKwargs('pkgconfig.generate', '0.59.0', ['unescaped_variables', 'unescaped_uninstalled_variables']) @FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables']) @FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags']) @@ -469,11 +484,12 @@ class PkgConfigModule(ExtensionModule): default_name = None mainlib = None default_subdirs = ['.'] + args, kwargs = PkgConfigModule._handle_both_libraries(args, kwargs) if not args and 'version' not in kwargs: FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject) elif len(args) == 1: FeatureNew.single_use('pkgconfig.generate optional positional argument', '0.46.0', state.subproject) - mainlib = getattr(args[0], 'held_object', args[0]) + mainlib = args[0] if not isinstance(mainlib, (build.StaticLibrary, build.SharedLibrary)): raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object') default_name = mainlib.name @@ -556,7 +572,7 @@ class PkgConfigModule(ExtensionModule): pkgroot = os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('libdir')), 'pkgconfig') if not isinstance(pkgroot, str): raise mesonlib.MesonException('Install_dir must be a string.') - self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, + self._generate_pkgconfig_file(state, deps, subdirs, name, description, url, version, pcfile, conflicts, variables, unescaped_variables, False, dataonly) res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot, None, state.subproject) @@ -566,7 +582,7 @@ class PkgConfigModule(ExtensionModule): unescaped_variables = parse_variable_list(unescaped_variables) pcfile = filebase + '-uninstalled.pc' - self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, + self._generate_pkgconfig_file(state, deps, subdirs, name, description, url, version, pcfile, conflicts, variables, unescaped_variables, uninstalled=True, dataonly=dataonly) # Associate the main library with this generated pc file. If the library diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index f3bcfab..46fd27b 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -50,6 +50,7 @@ class PythonDependency(SystemDependency): self.variables = python_holder.variables self.paths = python_holder.paths self.link_libpython = python_holder.link_libpython + self.info: T.Optional[T.Dict[str, str]] = None if mesonlib.version_compare(self.version, '>= 3.0'): self.major_version = 3 else: @@ -278,12 +279,20 @@ print (json.dumps ({ })) ''' +class PythonExternalProgram(ExternalProgram): + def __init__(self, name: str, command: T.Optional[T.List[str]] = None, ext_prog: T.Optional[ExternalProgram] = None): + if ext_prog is None: + super().__init__(name, command=command, silent=True) + else: + self.name = ext_prog.name + self.command = ext_prog.command + self.path = ext_prog.path + self.info: T.Dict[str, str] = {} class PythonInstallation(ExternalProgramHolder): - def __init__(self, interpreter, python, info): - ExternalProgramHolder.__init__(self, python, interpreter.subproject) - self.interpreter = interpreter - self.subproject = self.interpreter.subproject + def __init__(self, python, interpreter): + ExternalProgramHolder.__init__(self, python, interpreter) + info = python.info prefix = self.interpreter.environment.coredata.get_option(mesonlib.OptionKey('prefix')) self.variables = info['variables'] self.paths = info['paths'] @@ -325,11 +334,10 @@ class PythonInstallation(ExternalProgramHolder): # behavior. See https://github.com/mesonbuild/meson/issues/4117 if not self.link_libpython: new_deps = [] - for holder in mesonlib.extract_as_list(kwargs, 'dependencies'): - dep = holder.held_object + for dep in mesonlib.extract_as_list(kwargs, 'dependencies'): if isinstance(dep, PythonDependency): - holder = self.interpreter.holderify(dep.get_partial_dependency(compile_args=True)) - new_deps.append(holder) + dep = dep.get_partial_dependency(compile_args=True) + new_deps.append(dep) kwargs['dependencies'] = new_deps suffix = self.variables.get('EXT_SUFFIX') or self.variables.get('SO') or self.variables.get('.so') @@ -360,7 +368,7 @@ class PythonInstallation(ExternalProgramHolder): dep = PythonDependency(self, self.interpreter.environment, kwargs) if required and not dep.found(): raise mesonlib.MesonException('Python dependency not found') - return self.interpreter.holderify(dep) + return dep @permittedKwargs(['pure', 'subdir']) def install_sources_method(self, args, kwargs): @@ -377,7 +385,7 @@ class PythonInstallation(ExternalProgramHolder): else: kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) - return self.interpreter.holderify(self.interpreter.func_install_data(None, args, kwargs)) + return self.interpreter.func_install_data(None, args, kwargs) @noPosargs @permittedKwargs(['pure', 'subdir']) @@ -519,25 +527,26 @@ class PythonModule(ExtensionModule): if disabled: mlog.log('Program', name_or_path or 'python', 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')') - return ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) + return NonExistingExternalProgram() if not name_or_path: - python = ExternalProgram('python3', mesonlib.python_command, silent=True) + python = PythonExternalProgram('python3', mesonlib.python_command) else: - python = ExternalProgram.from_entry('python3', name_or_path) + tmp_python = ExternalProgram.from_entry('python3', name_or_path) + python = PythonExternalProgram('python3', ext_prog=tmp_python) if not python.found() and mesonlib.is_windows(): pythonpath = self._get_win_pythonpath(name_or_path) if pythonpath is not None: name_or_path = pythonpath - python = ExternalProgram(name_or_path, silent=True) + python = PythonExternalProgram(name_or_path) # Last ditch effort, python2 or python3 can be named python # on various platforms, let's not give up just yet, if an executable # named python is available and has a compatible version, let's use # it if not python.found() and name_or_path in ['python2', 'python3']: - python = ExternalProgram('python', silent=True) + python = PythonExternalProgram('python') if python.found() and want_modules: for mod in want_modules: @@ -566,11 +575,11 @@ class PythonModule(ExtensionModule): if not python.found(): if required: raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python')) - res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) + return NonExistingExternalProgram() elif missing_modules: if required: raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules))) - res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) + return NonExistingExternalProgram() else: # Sanity check, we expect to have something that at least quacks in tune try: @@ -586,14 +595,17 @@ class PythonModule(ExtensionModule): mlog.debug(stderr) if isinstance(info, dict) and 'version' in info and self._check_version(name_or_path, info['version']): - res = PythonInstallation(self.interpreter, python, info) + python.info = info + return python else: - res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) if required: raise mesonlib.MesonException(f'{python} is not a valid python or it is missing setuptools') + return NonExistingExternalProgram() - return res + raise mesonlib.MesonBugException('Unreachable code was reached (PythonModule.find_installation).') def initialize(*args, **kwargs): - return PythonModule(*args, **kwargs) + mod = PythonModule(*args, **kwargs) + mod.interpreter.append_holder_map(PythonExternalProgram, PythonInstallation) + return mod diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 7d752db..ae45e4d 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from mesonbuild import coredata import os import shutil import typing as T @@ -20,21 +21,19 @@ import xml.etree.ElementTree as ET from . import ModuleReturnValue, ExtensionModule from .. import build -from .. import mesonlib from .. import mlog -from ..dependencies import find_external_dependency +from ..dependencies import find_external_dependency, Dependency, ExternalLibrary +from ..mesonlib import MesonException, File, FileOrString, version_compare, Popen_safe +from . import ModuleReturnValue, ExtensionModule from ..interpreter import extract_required_kwarg -from ..interpreter.interpreterobjects import DependencyHolder, ExternalLibraryHolder, IncludeDirsHolder, FeatureOptionHolder, GeneratedListHolder from ..interpreterbase import ContainerTypeInfo, FeatureDeprecated, KwargInfo, noPosargs, FeatureNew, typed_kwargs -from ..mesonlib import MesonException, File -from ..programs import NonExistingExternalProgram +from ..programs import ExternalProgram, NonExistingExternalProgram if T.TYPE_CHECKING: from . import ModuleState from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency from ..interpreter import Interpreter from ..interpreter import kwargs - from ..programs import ExternalProgram QtDependencyType = T.Union[QtPkgConfigDependency, QmakeQtDependency] @@ -45,7 +44,7 @@ if T.TYPE_CHECKING: """Keyword arguments for the Resource Compiler method.""" name: T.Optional[str] - sources: T.List[mesonlib.FileOrString] + sources: T.List[FileOrString] extra_args: T.List[str] method: str @@ -53,7 +52,7 @@ if T.TYPE_CHECKING: """Keyword arguments for the Ui Compiler method.""" - sources: T.List[mesonlib.FileOrString] + sources: T.List[FileOrString] extra_args: T.List[str] method: str @@ -61,25 +60,25 @@ if T.TYPE_CHECKING: """Keyword arguments for the Moc Compiler method.""" - sources: T.List[mesonlib.FileOrString] - headers: T.List[mesonlib.FileOrString] + sources: T.List[FileOrString] + headers: T.List[FileOrString] extra_args: T.List[str] method: str - include_directories: T.List[IncludeDirsHolder] - dependencies: T.List[T.Union[DependencyHolder, ExternalLibraryHolder]] + include_directories: T.List[T.Union[str, build.IncludeDirs]] + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] class PreprocessKwArgs(TypedDict): - sources: T.List[mesonlib.FileOrString] - moc_sources: T.List[mesonlib.FileOrString] - moc_headers: T.List[mesonlib.FileOrString] - qresources: T.List[mesonlib.FileOrString] - ui_files: T.List[mesonlib.FileOrString] + sources: T.List[FileOrString] + moc_sources: T.List[FileOrString] + moc_headers: T.List[FileOrString] + qresources: T.List[FileOrString] + ui_files: T.List[FileOrString] moc_extra_arguments: T.List[str] rcc_extra_arguments: T.List[str] uic_extra_arguments: T.List[str] - include_directories: T.List[IncludeDirsHolder] - dependencies: T.List[T.Union[DependencyHolder, ExternalLibraryHolder]] + include_directories: T.List[T.Union[str, build.IncludeDirs]] + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] method: str class HasToolKwArgs(kwargs.ExtractRequired): @@ -104,10 +103,10 @@ class QtBaseModule(ExtensionModule): def __init__(self, interpreter: 'Interpreter', qt_version: int = 5): ExtensionModule.__init__(self, interpreter) self.qt_version = qt_version - self.moc: 'ExternalProgram' = NonExistingExternalProgram('moc') - self.uic: 'ExternalProgram' = NonExistingExternalProgram('uic') - self.rcc: 'ExternalProgram' = NonExistingExternalProgram('rcc') - self.lrelease: 'ExternalProgram' = NonExistingExternalProgram('lrelease') + self.moc: ExternalProgram = NonExistingExternalProgram('moc') + self.uic: ExternalProgram = NonExistingExternalProgram('uic') + self.rcc: ExternalProgram = NonExistingExternalProgram('rcc') + self.lrelease: ExternalProgram = NonExistingExternalProgram('lrelease') self.methods.update({ 'has_tools': self.has_tools, 'preprocess': self.preprocess, @@ -141,14 +140,14 @@ class QtBaseModule(ExtensionModule): if name == 'lrelease': arg = ['-version'] - elif mesonlib.version_compare(qt_dep.version, '>= 5'): + elif version_compare(qt_dep.version, '>= 5'): arg = ['--version'] else: arg = ['-v'] # Ensure that the version of qt and each tool are the same - def get_version(p: 'ExternalProgram') -> str: - _, out, err = mesonlib.Popen_safe(p.get_command() + arg) + def get_version(p: ExternalProgram) -> str: + _, out, err = Popen_safe(p.get_command() + arg) if b.startswith('lrelease') or not qt_dep.version.startswith('4'): care = out else: @@ -157,7 +156,7 @@ class QtBaseModule(ExtensionModule): p = state.find_program(b, required=False, version_func=get_version, - wanted=wanted).held_object + wanted=wanted) if p.found(): setattr(self, name, p) @@ -172,7 +171,7 @@ class QtBaseModule(ExtensionModule): if qt.found(): # Get all tools and then make sure that they are the right version self.compilers_detect(state, qt) - if mesonlib.version_compare(qt.version, '>=5.14.0'): + if version_compare(qt.version, '>=5.14.0'): self._rcc_supports_depfiles = True else: mlog.warning('rcc dependencies will not work properly until you move to Qt >= 5.14:', @@ -185,7 +184,7 @@ class QtBaseModule(ExtensionModule): self.lrelease = NonExistingExternalProgram(name='lrelease' + suffix) @staticmethod - def _qrc_nodes(state: 'ModuleState', rcc_file: 'mesonlib.FileOrString') -> T.Tuple[str, T.List[str]]: + def _qrc_nodes(state: 'ModuleState', rcc_file: 'FileOrString') -> T.Tuple[str, T.List[str]]: abspath: str if isinstance(rcc_file, str): abspath = os.path.join(state.environment.source_dir, state.subdir, rcc_file) @@ -210,7 +209,7 @@ class QtBaseModule(ExtensionModule): except Exception: raise MesonException(f'Unable to parse resource file {abspath}') - def _parse_qrc_deps(self, state: 'ModuleState', rcc_file: 'mesonlib.FileOrString') -> T.List[File]: + def _parse_qrc_deps(self, state: 'ModuleState', rcc_file: 'FileOrString') -> T.List[File]: rcc_dirname, nodes = self._qrc_nodes(state, rcc_file) result: T.List[File] = [] for resource_path in nodes: @@ -243,7 +242,7 @@ class QtBaseModule(ExtensionModule): @noPosargs @typed_kwargs( 'qt.has_tools', - KwargInfo('required', (bool, FeatureOptionHolder), default=False), + KwargInfo('required', (bool, coredata.UserFeatureOption), default=False), KwargInfo('method', str, default='auto'), ) def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool: @@ -351,7 +350,7 @@ class QtBaseModule(ExtensionModule): kwargs['extra_args'] + ['-o', '@OUTPUT@', '@INPUT@'], ['ui_@BASENAME@.h'], name=f'Qt{self.qt_version} ui') - out = GeneratedListHolder(gen.process_files(kwargs['sources'], state)) + out = gen.process_files(kwargs['sources'], state) return ModuleReturnValue(out, [out]) @FeatureNew('qt.compile_moc', '0.59.0') @@ -362,8 +361,8 @@ class QtBaseModule(ExtensionModule): KwargInfo('headers', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), KwargInfo('method', str, default='auto'), - KwargInfo('include_directories', ContainerTypeInfo(list, IncludeDirsHolder), listify=True, default=[]), - KwargInfo('dependencies', ContainerTypeInfo(list, (DependencyHolder, ExternalLibraryHolder)), listify=True, default=[]), + KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), ) def compile_moc(self, state: 'ModuleState', args: T.Tuple, kwargs: 'MocCompilerKwArgs') -> ModuleReturnValue: self._detect_tools(state, kwargs['method']) @@ -378,7 +377,7 @@ class QtBaseModule(ExtensionModule): inc = state.get_include_args(include_dirs=kwargs['include_directories']) compile_args: T.List[str] = [] for dep in kwargs['dependencies']: - compile_args.extend([a for a in dep.held_object.get_all_compile_args() if a.startswith(('-I', '-D'))]) + compile_args.extend([a for a in dep.get_all_compile_args() if a.startswith(('-I', '-D'))]) output: T.List[build.GeneratedList] = [] @@ -408,8 +407,8 @@ class QtBaseModule(ExtensionModule): KwargInfo('rcc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), KwargInfo('uic_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), KwargInfo('method', str, default='auto'), - KwargInfo('include_directories', ContainerTypeInfo(list, IncludeDirsHolder), listify=True, default=[]), - KwargInfo('dependencies', ContainerTypeInfo(list, (DependencyHolder, ExternalLibraryHolder)), listify=True, default=[]), + KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), ) def preprocess(self, state: 'ModuleState', args: T.List[T.Union[str, File]], kwargs: 'PreprocessKwArgs') -> ModuleReturnValue: _sources = args[1:] @@ -428,11 +427,11 @@ class QtBaseModule(ExtensionModule): if not isinstance(args[0], str): raise build.InvalidArguments('First argument to qt.preprocess must be a string') rcc_kwargs['name'] = args[0] - sources.extend(self.compile_resources(state, tuple(), rcc_kwargs).return_value) + sources.append(self.compile_resources(state, tuple(), rcc_kwargs).return_value) if kwargs['ui_files']: ui_kwargs: 'UICompilerKwArgs' = {'sources': kwargs['ui_files'], 'extra_args': kwargs['uic_extra_arguments'], 'method': method} - sources.extend(self.compile_ui(state, tuple(), ui_kwargs).return_value) + sources.append(self.compile_ui(state, tuple(), ui_kwargs).return_value) if kwargs['moc_headers'] or kwargs['moc_sources']: moc_kwargs: 'MocCompilerKwArgs' = { @@ -443,7 +442,7 @@ class QtBaseModule(ExtensionModule): 'dependencies': kwargs['dependencies'], 'method': method, } - sources.extend(self.compile_moc(state, tuple(), moc_kwargs).return_value) + sources.append(self.compile_moc(state, tuple(), moc_kwargs).return_value) return ModuleReturnValue(sources, [sources]) diff --git a/mesonbuild/modules/sourceset.py b/mesonbuild/modules/sourceset.py index e413e32..ba8b300 100644 --- a/mesonbuild/modules/sourceset.py +++ b/mesonbuild/modules/sourceset.py @@ -14,16 +14,13 @@ from collections import namedtuple from .. import mesonlib +from .. import build from ..mesonlib import listify, OrderedSet from . import ExtensionModule, ModuleObject, MutableModuleObject from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, InterpreterException, InvalidArguments, InvalidCode, FeatureNew, ) -from ..interpreter import ( - GeneratedListHolder, CustomTargetHolder, - CustomTargetIndexHolder -) SourceSetRule = namedtuple('SourceSetRule', 'keys sources if_false sourcesets dependencies extra_deps') SourceFiles = namedtuple('SourceFiles', 'sources dependencies') @@ -49,8 +46,8 @@ class SourceSet(MutableModuleObject): deps = [] for x in arg: if isinstance(x, (str, mesonlib.File, - GeneratedListHolder, CustomTargetHolder, - CustomTargetIndexHolder)): + build.GeneratedList, build.CustomTarget, + build.CustomTargetIndex)): sources.append(x) elif hasattr(x, 'found'): if not allow_deps: @@ -101,7 +98,6 @@ class SourceSet(MutableModuleObject): if_true = args elif args: raise InterpreterException('add_all called with both positional and keyword arguments') - if_true = mesonlib.unholder(if_true) keys, dependencies = self.check_conditions(when) for s in if_true: if not isinstance(s, SourceSet): diff --git a/mesonbuild/modules/unstable_cuda.py b/mesonbuild/modules/unstable_cuda.py index af65af3..a80f9ca 100644 --- a/mesonbuild/modules/unstable_cuda.py +++ b/mesonbuild/modules/unstable_cuda.py @@ -16,8 +16,7 @@ import typing as T import re from ..mesonlib import version_compare -from ..interpreter import CompilerHolder -from ..compilers import CudaCompiler +from ..compilers import CudaCompiler, Compiler from . import ModuleObject @@ -83,7 +82,7 @@ class CudaModule(ModuleObject): @permittedKwargs(['detected']) def nvcc_arch_flags(self, state: 'ModuleState', - args: T.Tuple[T.Union[CompilerHolder, CudaCompiler, str]], + args: T.Tuple[T.Union[Compiler, CudaCompiler, str]], kwargs: T.Dict[str, T.Any]) -> T.List[str]: nvcc_arch_args = self._validate_nvcc_arch_args(args, kwargs) ret = self._nvcc_arch_flags(*nvcc_arch_args)[0] @@ -91,7 +90,7 @@ class CudaModule(ModuleObject): @permittedKwargs(['detected']) def nvcc_arch_readable(self, state: 'ModuleState', - args: T.Tuple[T.Union[CompilerHolder, CudaCompiler, str]], + args: T.Tuple[T.Union[Compiler, CudaCompiler, str]], kwargs: T.Dict[str, T.Any]) -> T.List[str]: nvcc_arch_args = self._validate_nvcc_arch_args(args, kwargs) ret = self._nvcc_arch_flags(*nvcc_arch_args)[1] @@ -105,16 +104,12 @@ class CudaModule(ModuleObject): @staticmethod def _detected_cc_from_compiler(c): - if isinstance(c, CompilerHolder): - c = c.compiler if isinstance(c, CudaCompiler): return c.detected_cc return '' @staticmethod def _version_from_compiler(c): - if isinstance(c, CompilerHolder): - c = c.compiler if isinstance(c, CudaCompiler): return c.version if isinstance(c, str): diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index f10c7aa..e997f6a 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -22,7 +22,6 @@ from ..mesonlib import (MesonException, Popen_safe, MachineChoice, get_variable_regex, do_replacement, extract_as_list) from ..interpreterbase import InterpreterException, FeatureNew from ..interpreterbase import permittedKwargs, typed_pos_args -from ..interpreter import DependencyHolder from ..compilers.compilers import CFLAGS_MAPPING, CEXE_MAPPING from ..dependencies import InternalDependency, PkgConfigDependency from ..mesonlib import OptionKey @@ -237,7 +236,7 @@ class ExternalProject(ModuleObject): variables = [] dep = InternalDependency(version, incdir, compile_args, link_args, libs, libs_whole, sources, final_deps, variables) - return DependencyHolder(dep, self.subproject) + return dep class ExternalProjectModule(ExtensionModule): diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py index f602e09..995370a 100644 --- a/mesonbuild/modules/unstable_rust.py +++ b/mesonbuild/modules/unstable_rust.py @@ -19,15 +19,11 @@ from . import ExtensionModule, ModuleReturnValue from .. import mlog from ..build import BuildTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, IncludeDirs, CustomTarget from ..interpreter.interpreter import TEST_KWARGS -from ..interpreter.interpreterobjects import ( - BuildTargetHolder, - CustomTargetHolder, - DependencyHolder, - ExecutableHolder, - ExternalLibraryHolder, -) from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, permittedKwargs, FeatureNew, typed_kwargs, typed_pos_args, noPosargs -from ..mesonlib import stringlistify, unholder, listify, typeslistify, File +from ..mesonlib import stringlistify, listify, typeslistify, File +from ..dependencies import Dependency, ExternalLibrary +from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, typed_pos_args, noPosargs +from ..mesonlib import stringlistify, listify, typeslistify, File if T.TYPE_CHECKING: from . import ModuleState @@ -38,7 +34,7 @@ if T.TYPE_CHECKING: class FuncTest(_kwargs.BaseTest): - dependencies: T.List[T.Union[DependencyHolder, ExternalLibraryHolder]] + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] is_parallel: bool @@ -55,18 +51,18 @@ class RustModule(ExtensionModule): 'bindgen': self.bindgen, }) - @typed_pos_args('rust.test', str, BuildTargetHolder) + @typed_pos_args('rust.test', str, BuildTarget) @typed_kwargs( 'rust.test', *TEST_KWARGS, KwargInfo('is_parallel', bool, default=False), KwargInfo( 'dependencies', - ContainerTypeInfo(list, (DependencyHolder, ExternalLibraryHolder)), + ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), ) - def test(self, state: 'ModuleState', args: T.Tuple[str, BuildTargetHolder], kwargs: 'FuncTest') -> ModuleReturnValue: + def test(self, state: 'ModuleState', args: T.Tuple[str, BuildTarget], kwargs: 'FuncTest') -> ModuleReturnValue: """Generate a rust test target from a given rust target. Rust puts it's unitests inside it's main source files, unlike most @@ -109,7 +105,7 @@ class RustModule(ExtensionModule): ``` """ name = args[0] - base_target: BuildTarget = unholder(args[1]) + base_target: BuildTarget = args[1] if not base_target.uses_rust(): raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target') extra_args = kwargs['args'] @@ -129,7 +125,7 @@ class RustModule(ExtensionModule): del extra_args[i] break - dependencies = [d.held_object for d in kwargs['dependencies']] + dependencies = [d for d in kwargs['dependencies']] # We need to cast here, as currently these don't have protocol in them, but test itself does. tkwargs = T.cast('_kwargs.FuncTest', kwargs.copy()) @@ -151,11 +147,10 @@ class RustModule(ExtensionModule): new_target_kwargs ) - e = ExecutableHolder(new_target, self.interpreter) test = self.interpreter.make_test( - self.interpreter.current_node, (name, e), tkwargs) + self.interpreter.current_node, (name, new_target), tkwargs) - return ModuleReturnValue(None, [e, test]) + return ModuleReturnValue(None, [new_target, test]) @noPosargs @permittedKwargs({'input', 'output', 'include_directories', 'c_args', 'args'}) @@ -168,7 +163,7 @@ class RustModule(ExtensionModule): header: 'SourceOutputs' _deps: T.Sequence['SourceOutputs'] try: - header, *_deps = unholder(self.interpreter.source_strings_to_files(listify(kwargs['input']))) + header, *_deps = self.interpreter.source_strings_to_files(listify(kwargs['input'])) except KeyError: raise InvalidArguments('rustmod.bindgen() `input` argument must have at least one element.') @@ -179,12 +174,12 @@ class RustModule(ExtensionModule): if not isinstance(output, str): raise InvalidArguments('rustmod.bindgen() `output` argument must be a string.') - include_dirs: T.List[IncludeDirs] = typeslistify(unholder(listify(kwargs.get('include_directories', []))), IncludeDirs) + include_dirs: T.List[IncludeDirs] = typeslistify(listify(kwargs.get('include_directories', [])), IncludeDirs) c_args: T.List[str] = stringlistify(listify(kwargs.get('c_args', []))) bind_args: T.List[str] = stringlistify(listify(kwargs.get('args', []))) # Split File and Target dependencies to add pass to CustomTarget - depends: T.List[T.Union[GeneratedList, BuildTarget, CustomTargetIndex]] = [] + depends: T.List[T.Union[GeneratedList, BuildTarget, CustomTargetIndex, CustomTarget]] = [] depend_files: T.List[File] = [] for d in _deps: if isinstance(d, File): @@ -198,8 +193,7 @@ class RustModule(ExtensionModule): inc_strs.extend([f'-I{x}' for x in i.to_string_list(state.environment.get_source_dir())]) if self._bindgen_bin is None: - # there's some bugs in the interpreter typeing. - self._bindgen_bin = state.find_program('bindgen').held_object + self._bindgen_bin = state.find_program('bindgen') name: str if isinstance(header, File): @@ -226,7 +220,7 @@ class RustModule(ExtensionModule): backend=state.backend, ) - return ModuleReturnValue([target], [CustomTargetHolder(target, self.interpreter)]) + return ModuleReturnValue([target], [target]) def initialize(*args: T.List, **kwargs: T.Dict) -> RustModule: diff --git a/mesonbuild/modules/unstable_simd.py b/mesonbuild/modules/unstable_simd.py index df5faa1..3339cea 100644 --- a/mesonbuild/modules/unstable_simd.py +++ b/mesonbuild/modules/unstable_simd.py @@ -54,11 +54,11 @@ class SimdModule(ExtensionModule): for key, value in kwargs.items(): if key not in self.isets and key != 'compiler': basic_kwargs[key] = value - compiler = kwargs['compiler'].compiler + compiler = kwargs['compiler'] if not isinstance(compiler, compilers.compilers.Compiler): raise mesonlib.MesonException('Compiler argument must be a compiler object.') cdata = self.interpreter.func_configuration_data(None, [], {}) - conf = cdata.held_object + conf = cdata.conf_data for iset in self.isets: if iset not in kwargs: continue diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 441fb9f..d322833 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -18,10 +18,9 @@ import re from .. import mlog from .. import mesonlib, build -from ..mesonlib import MachineChoice, MesonException, extract_as_list, unholder +from ..mesonlib import MachineChoice, MesonException, extract_as_list from . import ModuleReturnValue from . import ExtensionModule -from ..interpreter import CustomTargetHolder from ..interpreterbase import permittedKwargs, FeatureNewKwargs, flatten from ..programs import ExternalProgram @@ -86,11 +85,13 @@ class WindowsModule(ExtensionModule): wrc_depend_files = extract_as_list(kwargs, 'depend_files', pop = True) wrc_depends = extract_as_list(kwargs, 'depends', pop = True) for d in wrc_depends: - if isinstance(d, CustomTargetHolder): - extra_args += state.get_include_args([d.outdir_include()]) + if isinstance(d, build.CustomTarget): + extra_args += state.get_include_args([ + build.IncludeDirs('', [], False, [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(d))]) + ]) inc_dirs = extract_as_list(kwargs, 'include_directories', pop = True) for incd in inc_dirs: - if not isinstance(incd.held_object, (str, build.IncludeDirs)): + if not isinstance(incd, (str, build.IncludeDirs)): raise MesonException('Resource include dirs should be include_directories().') extra_args += state.get_include_args(inc_dirs) @@ -120,7 +121,6 @@ class WindowsModule(ExtensionModule): for subsrc in src: add_target(subsrc) return - src = unholder(src) if isinstance(src, str): name_formatted = src diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index 1d93b8a..bb14f96 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -19,6 +19,7 @@ import os import shutil import stat import sys +import re import typing as T from pathlib import Path @@ -28,9 +29,10 @@ from .mesonlib import MachineChoice if T.TYPE_CHECKING: from .environment import Environment + from .interpreter import Interpreter -class ExternalProgram: +class ExternalProgram(mesonlib.HoldableObject): """A program that is found on the system.""" @@ -41,7 +43,8 @@ class ExternalProgram: silent: bool = False, search_dir: T.Optional[str] = None, extra_search_dirs: T.Optional[T.List[str]] = None): self.name = name - self.path = None # type: T.Optional[str] + self.path: T.Optional[str] = None + self.cached_version: T.Optional[str] = None if command is not None: self.command = mesonlib.listify(command) if mesonlib.is_windows(): @@ -97,6 +100,24 @@ class ExternalProgram: '''Human friendly description of the command''' return ' '.join(self.command) + def get_version(self, interpreter: 'Interpreter') -> str: + if not self.cached_version: + raw_cmd = self.get_command() + ['--version'] + cmd: T.List[T.Union[str, ExternalProgram]] = [self, '--version'] + res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) + if res.returncode != 0: + m = 'Running {!r} failed' + raise mesonlib.MesonException(m.format(raw_cmd)) + output = res.stdout.strip() + if not output: + output = res.stderr.strip() + match = re.search(r'([0-9][0-9\.]+)', output) + if not match: + m = 'Could not find a version number in output of {!r}' + raise mesonlib.MesonException(m.format(raw_cmd)) + self.cached_version = match.group(1) + return self.cached_version + @classmethod def from_bin_list(cls, env: 'Environment', for_machine: MachineChoice, name: str) -> 'ExternalProgram': # There is a static `for_machine` for this class because the binary diff --git a/run_mypy.py b/run_mypy.py index 982a3ae..e780adf 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -11,7 +11,7 @@ from mesonbuild.mesonlib import version_compare modules = [ # fully typed submodules - 'mesonbuild/ast', + # 'mesonbuild/ast', 'mesonbuild/cmake', 'mesonbuild/compilers', 'mesonbuild/dependencies', @@ -23,6 +23,7 @@ modules = [ 'mesonbuild/arglist.py', # 'mesonbuild/coredata.py', 'mesonbuild/envconfig.py', + 'mesonbuild/interpreter/interpreterobjects.py', 'mesonbuild/linkers.py', 'mesonbuild/mcompile.py', 'mesonbuild/mdevenv.py', diff --git a/run_project_tests.py b/run_project_tests.py index d6dc5e1..3522009 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -224,6 +224,7 @@ class InstalledFile: @functools.total_ordering class TestDef: def __init__(self, path: Path, name: T.Optional[str], args: T.List[str], skip: bool = False): + self.category = path.parts[1] self.path = path self.name = name self.args = args @@ -233,6 +234,9 @@ class TestDef: self.do_not_set_opts = [] # type: T.List[str] self.stdout = [] # type: T.List[T.Dict[str, str]] + # Always print a stack trace for Meson exceptions + self.env['MESON_FORCE_BACKTRACE'] = '1' + def __repr__(self) -> str: return '<{}: {:<48} [{}: {}] -- {}>'.format(type(self).__name__, str(self.path), self.name, self.args, self.skip) @@ -619,7 +623,7 @@ def _run_test(test: TestDef, gen_args.extend(['--native-file', nativefile.as_posix()]) if crossfile.exists(): gen_args.extend(['--cross-file', crossfile.as_posix()]) - (returncode, stdo, stde) = run_configure(gen_args, env=test.env) + (returncode, stdo, stde) = run_configure(gen_args, env=test.env, catch_exception=True) try: logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt') mesonlog = logfile.open(errors='ignore', encoding='utf-8').read() @@ -1267,9 +1271,9 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], continue # Handle skipped tests - if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(name, t.path.as_posix()))): + if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(t.category, t.path.as_posix()))): f.update_log(TestStatus.SKIP) - current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': name}) + current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': t.category}) ET.SubElement(current_test, 'skipped', {}) skipped_tests += 1 continue @@ -1280,6 +1284,15 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], safe_print(bold('During:'), result.step.name) safe_print(bold('Reason:'), result.msg) failing_tests += 1 + # Append a visual seperator for the different test cases + cols = shutil.get_terminal_size((100, 20)).columns + name_str = ' '.join([str(x) for x in f.testdef.display_name()]) + name_len = len(re.sub(r'\x1B[^m]+m', '', name_str)) # Do not count escape sequences + left_w = (cols // 2) - (name_len // 2) - 1 + left_w = max(3, left_w) + right_w = cols - left_w - name_len - 2 + right_w = max(3, right_w) + failing_logs.append(f'\n\x1b[31m{"="*left_w}\x1b[0m {name_str} \x1b[31m{"="*right_w}\x1b[0m\n') if result.step == BuildStep.configure and result.mlog != no_meson_log_msg: # For configure failures, instead of printing stdout, # print the meson log if available since it's a superset @@ -1311,7 +1324,7 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], current_test = ET.SubElement( current_suite, 'testcase', - {'name': testname, 'classname': name, 'time': '%.3f' % total_time} + {'name': testname, 'classname': t.category, 'time': '%.3f' % total_time} ) if result.msg != '': ET.SubElement(current_test, 'failure', {'message': result.msg}) diff --git a/run_single_test.py b/run_single_test.py index 703f0a6..4d70507 100755 --- a/run_single_test.py +++ b/run_single_test.py @@ -57,7 +57,7 @@ def main() -> None: failed = True else: msg = mlog.green('PASS:') - mlog.log(msg, test.display_name()) + mlog.log(msg, *test.display_name()) if result is not None and result.msg and 'MESON_SKIP_TEST' not in result.stdo: mlog.log('reason:', result.msg) if result.step is BuildStep.configure: diff --git a/run_tests.py b/run_tests.py index 80ea38f..10d63df 100755 --- a/run_tests.py +++ b/run_tests.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import collections import os import sys import time @@ -22,6 +23,7 @@ import subprocess import tempfile import platform import argparse +import traceback from io import StringIO from enum import Enum from glob import glob @@ -267,13 +269,21 @@ def clear_meson_configure_class_caches() -> None: compilers.CCompiler.find_framework_cache = {} dependencies.PkgConfigDependency.pkgbin_cache = {} dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None) + mesonlib.project_meson_versions = collections.defaultdict(str) -def run_configure_inprocess(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str, str]: +def run_configure_inprocess(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[int, str, str]: stderr = StringIO() stdout = StringIO() + returncode = 0 with mock.patch.dict(os.environ, env or {}), mock.patch.object(sys, 'stdout', stdout), mock.patch.object(sys, 'stderr', stderr): try: returncode = mesonmain.run(commandlist, get_meson_script()) + except Exception: + if catch_exception: + returncode = 1 + traceback.print_exc() + else: + raise finally: clear_meson_configure_class_caches() return returncode, stdout.getvalue(), stderr.getvalue() @@ -282,11 +292,11 @@ def run_configure_external(full_command: T.List[str], env: T.Optional[T.Dict[str pc, o, e = mesonlib.Popen_safe(full_command, env=env) return pc.returncode, o, e -def run_configure(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str, str]: +def run_configure(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[int, str, str]: global meson_exe if meson_exe: return run_configure_external(meson_exe + commandlist, env=env) - return run_configure_inprocess(commandlist, env=env) + return run_configure_inprocess(commandlist, env=env, catch_exception=catch_exception) def print_system_info(): print(mlog.bold('System information.')) diff --git a/run_unittests.py b/run_unittests.py index 8c20e72..b55ba96 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -727,23 +727,20 @@ 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])) self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]])) - def test_unholder(self): - unholder = mesonbuild.mesonlib.unholder - - holder1 = ObjectHolder(1) - holder3 = ObjectHolder(3) - holders = [holder1, holder3] - - self.assertEqual(1, unholder(holder1)) - self.assertEqual([1], unholder([holder1])) - self.assertEqual([1, 3], unholder(holders)) - def test_extract_as_list(self): extract = mesonbuild.mesonlib.extract_as_list # Test sanity @@ -753,8 +750,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 +774,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") @@ -6165,7 +6169,7 @@ class FailureTests(BasePlatformTests): ''' tdir = os.path.join(self.unit_test_dir, '21 exit status') with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1'}) + self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1', 'MESON_FORCE_BACKTRACE': ''}) self.assertEqual(cm.exception.returncode, 2) self.wipe() diff --git a/test cases/cmake/25 assembler/main.c b/test cases/cmake/25 assembler/main.c new file mode 100644 index 0000000..5aef967 --- /dev/null +++ b/test cases/cmake/25 assembler/main.c @@ -0,0 +1,18 @@ +#include <stdint.h> +#include <stdio.h> + +int32_t cmTestFunc(void); + +int main(void) +{ + if (cmTestFunc() > 4200) + { + printf("Test success.\n"); + return 0; + } + else + { + printf("Test failure.\n"); + return 1; + } +} diff --git a/test cases/cmake/25 assembler/meson.build b/test cases/cmake/25 assembler/meson.build new file mode 100644 index 0000000..7180356 --- /dev/null +++ b/test cases/cmake/25 assembler/meson.build @@ -0,0 +1,9 @@ +project('assembler test', ['c', 'cpp']) + +cm = import('cmake') + +sub_pro = cm.subproject('cmTest') +sub_dep = sub_pro.dependency('cmTest') + +exe1 = executable('exe1', ['main.c'], dependencies: [sub_dep]) +test('test1', exe1) diff --git a/test cases/cmake/25 assembler/subprojects/cmTest/CMakeLists.txt b/test cases/cmake/25 assembler/subprojects/cmTest/CMakeLists.txt new file mode 100644 index 0000000..5fb7cd6 --- /dev/null +++ b/test cases/cmake/25 assembler/subprojects/cmTest/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmTest) + +#Detect processor +if ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "amd64") + SET(TEST_PROCESSOR "x86_64") +elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "x86_64") + SET(TEST_PROCESSOR "x86_64") +elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "i386") + SET(TEST_PROCESSOR "x86") +elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "i686") + SET(TEST_PROCESSOR "x86") +elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "arm") + SET(TEST_PROCESSOR "arm") +elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "aarch64") + SET(TEST_PROCESSOR "arm") +else () + message(FATAL_ERROR, 'MESON_SKIP_TEST: Unsupported Assembler Platform') +endif () + +#Detect ABI +if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + SET(TEST_ABI "sysv") +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") + SET(TEST_ABI "sysv") +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "NetBSD") + SET(TEST_ABI "sysv") +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD") + SET(TEST_ABI "sysv") +else () + message(FATAL_ERROR, 'MESON_SKIP_TEST: Unsupported Assembler Platform') +endif () + +SET(TEST_PLATFORM "${TEST_PROCESSOR}-${TEST_ABI}") + +if ( ("${TEST_PLATFORM}" MATCHES "x86_64-sysv") + OR ("${TEST_PLATFORM}" MATCHES "x86-sysv") + OR ("${TEST_PLATFORM}" MATCHES "arm-sysv")) + SET(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) + enable_language(ASM) + SET(TEST_SOURCE "cmTestAsm.s") +endif () + +add_library(cmTest STATIC cmTest.c ${TEST_SOURCE}) diff --git a/test cases/cmake/25 assembler/subprojects/cmTest/cmTest.c b/test cases/cmake/25 assembler/subprojects/cmTest/cmTest.c new file mode 100644 index 0000000..e32415c --- /dev/null +++ b/test cases/cmake/25 assembler/subprojects/cmTest/cmTest.c @@ -0,0 +1,8 @@ +#include <stdint.h> + +extern const int32_t cmTestArea; + +int32_t cmTestFunc(void) +{ + return cmTestArea; +} diff --git a/test cases/cmake/25 assembler/subprojects/cmTest/cmTestAsm.s b/test cases/cmake/25 assembler/subprojects/cmTest/cmTestAsm.s new file mode 100644 index 0000000..8aa83a6 --- /dev/null +++ b/test cases/cmake/25 assembler/subprojects/cmTest/cmTestAsm.s @@ -0,0 +1,4 @@ +.text +.globl cmTestArea +cmTestArea: + .long 4242 diff --git a/test cases/common/178 bothlibraries/meson.build b/test cases/common/178 bothlibraries/meson.build index 0bfba76..d52158d 100644 --- a/test cases/common/178 bothlibraries/meson.build +++ b/test cases/common/178 bothlibraries/meson.build @@ -20,6 +20,9 @@ exe_static2 = executable('prog-static2', 'main.c', link_with : both_libs2.get_static_lib()) exe_both2 = executable('prog-both2', 'main.c', link_with : both_libs2) +# Ensure that calling the build target methods also works +assert(both_libs.name() == 'mylib') + test('runtest-shared-2', exe_shared2) test('runtest-static-2', exe_static2) test('runtest-both-2', exe_both2) diff --git a/test cases/common/182 find override/meson.build b/test cases/common/182 find override/meson.build index b277459..8dcbac7 100644 --- a/test cases/common/182 find override/meson.build +++ b/test cases/common/182 find override/meson.build @@ -1,8 +1,10 @@ project('find program override', 'c') gencodegen = find_program('gencodegen', required : false) +six_prog = find_program('six_meson_exe', required : false) assert(not gencodegen.found(), 'gencodegen is an internal program, should not be found') +assert(not six_prog.found(), 'six_meson_exe is an internal program, should not be found') # Test the check-if-found-else-override workflow if not gencodegen.found() @@ -13,3 +15,11 @@ subdir('otherdir') tool = find_program('sometool') assert(tool.found()) +assert(tool.full_path() != '') +assert(tool.full_path() == tool.path()) + +# six_meson_exe is an overritten project executable +six_prog = find_program('six_meson_exe') +assert(six_prog.found()) +assert(six_prog.full_path() != '') +assert(six_prog.full_path() == six_prog.path()) diff --git a/test cases/common/182 find override/otherdir/meson.build b/test cases/common/182 find override/otherdir/meson.build index 5cefc88..7deff40 100644 --- a/test cases/common/182 find override/otherdir/meson.build +++ b/test cases/common/182 find override/otherdir/meson.build @@ -10,6 +10,10 @@ e = executable('six', 'main.c', src) test('six', e) +# Override stuff with an executables +meson.override_find_program('six_meson_exe', e) + + # The same again, but this time with a program that was generated # with configure_file. diff --git a/test cases/failing/115 compiler argument checking/meson.build b/test cases/failing/115 compiler argument checking/meson.build new file mode 100644 index 0000000..bb1f447 --- /dev/null +++ b/test cases/failing/115 compiler argument checking/meson.build @@ -0,0 +1,4 @@ +project('compiler argument checking test', 'c') + +cc = meson.get_compiler('c') +add_project_arguments(cc.get_supported_arguments('-meson-goober-arg-for-testing', checked : 'require'), language : 'c') diff --git a/test cases/failing/115 compiler argument checking/test.json b/test cases/failing/115 compiler argument checking/test.json new file mode 100644 index 0000000..93f9476 --- /dev/null +++ b/test cases/failing/115 compiler argument checking/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/115 compiler argument checking/meson.build:4:0: ERROR: Compiler for C does not support \"-meson-goober-arg-for-testing\"" + } + ] +} diff --git a/test cases/failing/115 empty fallback/meson.build b/test cases/failing/115 empty fallback/meson.build new file mode 100644 index 0000000..f4eb5fe --- /dev/null +++ b/test cases/failing/115 empty fallback/meson.build @@ -0,0 +1,6 @@ +project('empty fallback') + +# There is a subproject named 'foo' that overrides that dependency, +# but `fallback: []` should not allow to use it. Same behaviour than with +# `allow_fallback: false` +dependency('foo', fallback: []) diff --git a/test cases/failing/115 empty fallback/subprojects/foo/meson.build b/test cases/failing/115 empty fallback/subprojects/foo/meson.build new file mode 100644 index 0000000..c9e134b --- /dev/null +++ b/test cases/failing/115 empty fallback/subprojects/foo/meson.build @@ -0,0 +1,3 @@ +project('foo') + +meson.override_dependency('foo', declare_dependency()) diff --git a/test cases/failing/115 empty fallback/test.json b/test cases/failing/115 empty fallback/test.json new file mode 100644 index 0000000..dcfde45 --- /dev/null +++ b/test cases/failing/115 empty fallback/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/115 empty fallback/meson.build:6:0: ERROR: Dependency \"foo\" not found, tried pkgconfig and cmake" + } + ] +} diff --git a/test cases/failing/45 abspath to srcdir/test.json b/test cases/failing/45 abspath to srcdir/test.json index b6a87fe..177bac1 100644 --- a/test cases/failing/45 abspath to srcdir/test.json +++ b/test cases/failing/45 abspath to srcdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/45 abspath to srcdir/meson.build:3:0: ERROR: Tried to form an absolute path to a source dir. You should not do that but use relative paths instead." + "line": "test cases/failing/45 abspath to srcdir/meson.build:3:0: ERROR: Tried to form an absolute path to a source dir." } ] } diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index 2e85ddb..f9fb21c 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -57,6 +57,10 @@ foreach qt : ['qt4', 'qt5', 'qt6'] # XML files that need to be compiled with the uic tol. prep += qtmodule.compile_ui(sources : 'mainWindow.ui', method: get_option('method')) + qtmodule.preprocess( + ui_files : 'mainWindow.ui', + method: get_option('method')) + # Resource file(s) for rcc compiler extra_cpp_args = [] if meson.is_unity() @@ -100,6 +104,12 @@ foreach qt : ['qt4', 'qt5', 'qt6'] # The build system needs to include the cpp files from # headers but the user must manually include moc # files from sources. + qtmodule.preprocess( + moc_extra_arguments : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `extra_arguments` + moc_sources : 'manualinclude.cpp', + moc_headers : 'manualinclude.h', + method : get_option('method')) + manpreprocessed = qtmodule.compile_moc( extra_args : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `extra_arguments` sources : 'manualinclude.cpp', diff --git a/test cases/native/9 override with exe/meson.build b/test cases/native/9 override with exe/meson.build index 62d2f32..5275532 100644 --- a/test cases/native/9 override with exe/meson.build +++ b/test cases/native/9 override with exe/meson.build @@ -1,4 +1,4 @@ -project('myexe', 'c') +project('myexe', 'c', version: '0.1') sub = subproject('sub') prog = find_program('foobar', version : '>= 2.0', required : false) |