1import os
2import shlex
3import subprocess
4import copy
5import textwrap
6
7from pathlib import Path, PurePath
8
9from .. import mesonlib
10from .. import coredata
11from .. import build
12from .. import mlog
13
14from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule
15from ..backend.backends import TestProtocol
16from ..interpreterbase import (
17                               ContainerTypeInfo, KwargInfo, MesonOperator,
18                               InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject,
19                               FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
20                               typed_pos_args, typed_kwargs, typed_operator, permittedKwargs,
21                               noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs,
22                               flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
23from ..interpreter.type_checking import NoneType
24from ..dependencies import Dependency, ExternalLibrary, InternalDependency
25from ..programs import ExternalProgram
26from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe
27
28import typing as T
29
30if T.TYPE_CHECKING:
31    from . import kwargs
32    from .interpreter import Interpreter
33    from ..envconfig import MachineInfo
34
35    from typing_extensions import TypedDict
36
37    class EnvironmentSeparatorKW(TypedDict):
38
39        separator: str
40
41
42def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired',
43                           subproject: str,
44                           feature_check: T.Optional[FeatureCheckBase] = None,
45                           default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]:
46    val = kwargs.get('required', default)
47    disabled = False
48    required = False
49    feature: T.Optional[str] = None
50    if isinstance(val, coredata.UserFeatureOption):
51        if not feature_check:
52            feature_check = FeatureNew('User option "feature"', '0.47.0')
53        feature_check.use(subproject)
54        feature = val.name
55        if val.is_disabled():
56            disabled = True
57        elif val.is_enabled():
58            required = True
59    elif isinstance(val, bool):
60        required = val
61    else:
62        raise InterpreterException('required keyword argument must be boolean or a feature option')
63
64    # Keep boolean value in kwargs to simplify other places where this kwarg is
65    # checked.
66    # TODO: this should be removed, and those callers should learn about FeatureOptions
67    kwargs['required'] = required
68
69    return disabled, required, feature
70
71def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]:
72    search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', []))
73    search_dirs = [Path(d).expanduser() for d in search_dirs_str]
74    for d in search_dirs:
75        if mesonlib.is_windows() and d.root.startswith('\\'):
76            # a Unix-path starting with `/` that is not absolute on Windows.
77            # discard without failing for end-user ease of cross-platform directory arrays
78            continue
79        if not d.is_absolute():
80            raise InvalidCode(f'Search directory {d} is not an absolute path.')
81    return list(map(str, search_dirs))
82
83class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]):
84    def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'):
85        super().__init__(option, interpreter)
86        if option and option.is_auto():
87            # TODO: we need to case here because options is not a TypedDict
88            self.held_object = T.cast(coredata.UserFeatureOption, self.env.coredata.options[OptionKey('auto_features')])
89            self.held_object.name = option.name
90        self.methods.update({'enabled': self.enabled_method,
91                             'disabled': self.disabled_method,
92                             'allowed': self.allowed_method,
93                             'auto': self.auto_method,
94                             'require': self.require_method,
95                             'disable_auto_if': self.disable_auto_if_method,
96                             })
97
98    @property
99    def value(self) -> str:
100        return 'disabled' if not self.held_object else self.held_object.value
101
102    def as_disabled(self) -> coredata.UserFeatureOption:
103        disabled = copy.deepcopy(self.held_object)
104        disabled.value = 'disabled'
105        return disabled
106
107    @noPosargs
108    @noKwargs
109    def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
110        return self.value == 'enabled'
111
112    @noPosargs
113    @noKwargs
114    def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
115        return self.value == 'disabled'
116
117    @noPosargs
118    @noKwargs
119    def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
120        return self.value != 'disabled'
121
122    @noPosargs
123    @noKwargs
124    def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
125        return self.value == 'auto'
126
127    @permittedKwargs({'error_message'})
128    def require_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption:
129        if len(args) != 1:
130            raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), ))
131        if not isinstance(args[0], bool):
132            raise InvalidArguments('boolean argument expected.')
133        error_message = kwargs.pop('error_message', '')
134        if error_message and not isinstance(error_message, str):
135            raise InterpreterException("Error message must be a string.")
136        if args[0]:
137            return copy.deepcopy(self.held_object)
138
139        assert isinstance(error_message, str)
140        if self.value == 'enabled':
141            prefix = f'Feature {self.held_object.name} cannot be enabled'
142            prefix = prefix + ': ' if error_message else ''
143            raise InterpreterException(prefix + error_message)
144        return self.as_disabled()
145
146    @noKwargs
147    def disable_auto_if_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption:
148        if len(args) != 1:
149            raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), ))
150        if not isinstance(args[0], bool):
151            raise InvalidArguments('boolean argument expected.')
152        return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled()
153
154
155class RunProcess(MesonInterpreterObject):
156
157    def __init__(self,
158                 cmd: ExternalProgram,
159                 args: T.List[str],
160                 env: build.EnvironmentVariables,
161                 source_dir: str,
162                 build_dir: str,
163                 subdir: str,
164                 mesonintrospect: T.List[str],
165                 in_builddir: bool = False,
166                 check: bool = False,
167                 capture: bool = True) -> None:
168        super().__init__()
169        if not isinstance(cmd, ExternalProgram):
170            raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
171        self.capture = capture
172        self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
173        self.methods.update({'returncode': self.returncode_method,
174                             'stdout': self.stdout_method,
175                             'stderr': self.stderr_method,
176                             })
177
178    def run_command(self,
179                    cmd: ExternalProgram,
180                    args: T.List[str],
181                    env: build.EnvironmentVariables,
182                    source_dir: str,
183                    build_dir: str,
184                    subdir: str,
185                    mesonintrospect: T.List[str],
186                    in_builddir: bool,
187                    check: bool = False) -> T.Tuple[int, str, str]:
188        command_array = cmd.get_command() + args
189        menv = {'MESON_SOURCE_ROOT': source_dir,
190                'MESON_BUILD_ROOT': build_dir,
191                'MESON_SUBDIR': subdir,
192                'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]),
193                }
194        if in_builddir:
195            cwd = os.path.join(build_dir, subdir)
196        else:
197            cwd = os.path.join(source_dir, subdir)
198        child_env = os.environ.copy()
199        child_env.update(menv)
200        child_env = env.get_env(child_env)
201        stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
202        mlog.debug('Running command:', ' '.join(command_array))
203        try:
204            p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
205            if self.capture:
206                mlog.debug('--- stdout ---')
207                mlog.debug(o)
208            else:
209                o = ''
210                mlog.debug('--- stdout disabled ---')
211            mlog.debug('--- stderr ---')
212            mlog.debug(e)
213            mlog.debug('')
214
215            if check and p.returncode != 0:
216                raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
217
218            return p.returncode, o, e
219        except FileNotFoundError:
220            raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
221
222    @noPosargs
223    @noKwargs
224    def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int:
225        return self.returncode
226
227    @noPosargs
228    @noKwargs
229    def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
230        return self.stdout
231
232    @noPosargs
233    @noKwargs
234    def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
235        return self.stderr
236
237
238_ENV_SEPARATOR_KW = KwargInfo('separator', str, default=os.pathsep)
239
240
241class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject):
242
243    def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'):
244        super().__init__(obj, interpreter)
245        self.methods.update({'set': self.set_method,
246                             'append': self.append_method,
247                             'prepend': self.prepend_method,
248                             })
249
250    def __repr__(self) -> str:
251        repr_str = "<{0}: {1}>"
252        return repr_str.format(self.__class__.__name__, self.held_object.envvars)
253
254    def __deepcopy__(self, memo: T.Dict[str, object]) -> 'EnvironmentVariablesHolder':
255        # Avoid trying to copy the interpreter
256        return EnvironmentVariablesHolder(copy.deepcopy(self.held_object), self.interpreter)
257
258    def warn_if_has_name(self, name: str) -> None:
259        # Multiple append/prepend operations was not supported until 0.58.0.
260        if self.held_object.has_name(name):
261            m = f'Overriding previous value of environment variable {name!r} with a new one'
262            FeatureNew(m, '0.58.0').use(self.subproject)
263
264    @typed_pos_args('environment.set', str, varargs=str, min_varargs=1)
265    @typed_kwargs('environment.set', _ENV_SEPARATOR_KW)
266    def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
267        name, values = args
268        self.held_object.set(name, values, kwargs['separator'])
269
270    @typed_pos_args('environment.append', str, varargs=str, min_varargs=1)
271    @typed_kwargs('environment.append', _ENV_SEPARATOR_KW)
272    def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
273        name, values = args
274        self.warn_if_has_name(name)
275        self.held_object.append(name, values, kwargs['separator'])
276
277    @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1)
278    @typed_kwargs('environment.prepend', _ENV_SEPARATOR_KW)
279    def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
280        name, values = args
281        self.warn_if_has_name(name)
282        self.held_object.prepend(name, values, kwargs['separator'])
283
284
285class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject):
286    def __init__(self, subproject: str, initial_values: T.Optional[T.Dict[str, T.Any]] = None) -> None:
287        self.used = False # These objects become immutable after use in configure_file.
288        super().__init__(subproject=subproject)
289        self.conf_data = build.ConfigurationData()
290        self.methods.update({'set': self.set_method,
291                             'set10': self.set10_method,
292                             'set_quoted': self.set_quoted_method,
293                             'has': self.has_method,
294                             'get': self.get_method,
295                             'keys': self.keys_method,
296                             'get_unquoted': self.get_unquoted_method,
297                             'merge_from': self.merge_from_method,
298                             })
299        if isinstance(initial_values, dict):
300            for k, v in initial_values.items():
301                self.set_method([k, v], {})
302        elif initial_values:
303            raise AssertionError('Unsupported ConfigurationDataObject initial_values')
304
305    def is_used(self) -> bool:
306        return self.used
307
308    def mark_used(self) -> None:
309        self.used = True
310
311    def validate_args(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[str, T.Union[str, int, bool], T.Optional[str]]:
312        if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
313            mlog.deprecation('Passing a list as the single argument to '
314                             'configuration_data.set is deprecated. This will '
315                             'become a hard error in the future.',
316                             location=self.current_node)
317            args = args[0]
318
319        if len(args) != 2:
320            raise InterpreterException("Configuration set requires 2 arguments.")
321        if self.used:
322            raise InterpreterException("Can not set values on configuration object that has been used.")
323        name, val = args
324        if not isinstance(val, (int, str)):
325            msg = f'Setting a configuration data value to {val!r} is invalid, ' \
326                  'and will fail at configure_file(). If you are using it ' \
327                  'just to store some values, please use a dict instead.'
328            mlog.deprecation(msg, location=self.current_node)
329        desc = kwargs.get('description', None)
330        if not isinstance(name, str):
331            raise InterpreterException("First argument to set must be a string.")
332        if desc is not None and not isinstance(desc, str):
333            raise InterpreterException('Description must be a string.')
334
335        # TODO: Remove the cast once we get rid of the deprecation
336        return name, T.cast(T.Union[str, bool, int], val), desc
337
338    @noArgsFlattening
339    def set_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
340        (name, val, desc) = self.validate_args(args, kwargs)
341        self.conf_data.values[name] = (val, desc)
342
343    def set_quoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
344        (name, val, desc) = self.validate_args(args, kwargs)
345        if not isinstance(val, str):
346            raise InterpreterException("Second argument to set_quoted must be a string.")
347        escaped_val = '\\"'.join(val.split('"'))
348        self.conf_data.values[name] = ('"' + escaped_val + '"', desc)
349
350    def set10_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
351        (name, val, desc) = self.validate_args(args, kwargs)
352        if val:
353            self.conf_data.values[name] = (1, desc)
354        else:
355            self.conf_data.values[name] = (0, desc)
356
357    def has_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
358        return args[0] in self.conf_data.values
359
360    @FeatureNew('configuration_data.get()', '0.38.0')
361    @noArgsFlattening
362    def get_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]:
363        if len(args) < 1 or len(args) > 2:
364            raise InterpreterException('Get method takes one or two arguments.')
365        if not isinstance(args[0], str):
366            raise InterpreterException('The variable name must be a string.')
367        name = args[0]
368        if name in self.conf_data:
369            return self.conf_data.get(name)[0]
370        if len(args) > 1:
371            # Assertion does not work because setting other values is still
372            # supported, but deprecated. Use T.cast in the meantime (even though
373            # this is a lie).
374            # TODO: Fix this once the deprecation is removed
375            # assert isinstance(args[1], (int, str, bool))
376            return T.cast(T.Union[str, int, bool], args[1])
377        raise InterpreterException('Entry %s not in configuration data.' % name)
378
379    @FeatureNew('configuration_data.get_unquoted()', '0.44.0')
380    def get_unquoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]:
381        if len(args) < 1 or len(args) > 2:
382            raise InterpreterException('Get method takes one or two arguments.')
383        if not isinstance(args[0], str):
384            raise InterpreterException('The variable name must be a string.')
385        name = args[0]
386        if name in self.conf_data:
387            val = self.conf_data.get(name)[0]
388        elif len(args) > 1:
389            assert isinstance(args[1], (str, int, bool))
390            val = args[1]
391        else:
392            raise InterpreterException('Entry %s not in configuration data.' % name)
393        if isinstance(val, str) and val[0] == '"' and val[-1] == '"':
394            return val[1:-1]
395        return val
396
397    def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]:
398        return self.conf_data.values[name]
399
400    @FeatureNew('configuration_data.keys()', '0.57.0')
401    @noPosargs
402    def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]:
403        return sorted(self.keys())
404
405    def keys(self) -> T.List[str]:
406        return list(self.conf_data.values.keys())
407
408    def merge_from_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None:
409        if len(args) != 1:
410            raise InterpreterException('Merge_from takes one positional argument.')
411        from_object_holder = args[0]
412        if not isinstance(from_object_holder, ConfigurationDataObject):
413            raise InterpreterException('Merge_from argument must be a configuration data object.')
414        from_object = from_object_holder.conf_data
415        for k, v in from_object.values.items():
416            self.conf_data.values[k] = v
417
418
419_PARTIAL_DEP_KWARGS = [
420    KwargInfo('compile_args', bool, default=False),
421    KwargInfo('link_args',    bool, default=False),
422    KwargInfo('links',        bool, default=False),
423    KwargInfo('includes',     bool, default=False),
424    KwargInfo('sources',      bool, default=False),
425]
426
427class DependencyHolder(ObjectHolder[Dependency]):
428    def __init__(self, dep: Dependency, interpreter: 'Interpreter'):
429        super().__init__(dep, interpreter)
430        self.methods.update({'found': self.found_method,
431                             'type_name': self.type_name_method,
432                             'version': self.version_method,
433                             'name': self.name_method,
434                             'get_pkgconfig_variable': self.pkgconfig_method,
435                             'get_configtool_variable': self.configtool_method,
436                             'get_variable': self.variable_method,
437                             'partial_dependency': self.partial_dependency_method,
438                             'include_type': self.include_type_method,
439                             'as_system': self.as_system_method,
440                             'as_link_whole': self.as_link_whole_method,
441                             })
442
443    def found(self) -> bool:
444        return self.found_method([], {})
445
446    @noPosargs
447    @noKwargs
448    def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
449        return self.held_object.type_name
450
451    @noPosargs
452    @noKwargs
453    def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
454        if self.held_object.type_name == 'internal':
455            return True
456        return self.held_object.found()
457
458    @noPosargs
459    @noKwargs
460    def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
461        return self.held_object.get_version()
462
463    @noPosargs
464    @noKwargs
465    def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
466        return self.held_object.get_name()
467
468    @FeatureDeprecated('Dependency.get_pkgconfig_variable', '0.56.0',
469                       'use Dependency.get_variable(pkgconfig : ...) instead')
470    @permittedKwargs({'define_variable', 'default'})
471    def pkgconfig_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
472        args = listify(args)
473        if len(args) != 1:
474            raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
475        varname = args[0]
476        if not isinstance(varname, str):
477            raise InterpreterException('Variable name must be a string.')
478        return self.held_object.get_pkgconfig_variable(varname, kwargs)
479
480    @FeatureNew('dep.get_configtool_variable', '0.44.0')
481    @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0',
482                       'use Dependency.get_variable(configtool : ...) instead')
483    @noKwargs
484    def configtool_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
485        args = listify(args)
486        if len(args) != 1:
487            raise InterpreterException('get_configtool_variable takes exactly one argument.')
488        varname = args[0]
489        if not isinstance(varname, str):
490            raise InterpreterException('Variable name must be a string.')
491        return self.held_object.get_configtool_variable(varname)
492
493    @FeatureNew('dep.partial_dependency', '0.46.0')
494    @noPosargs
495    @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS)
496    def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency:
497        pdep = self.held_object.get_partial_dependency(**kwargs)
498        return pdep
499
500    @FeatureNew('dep.get_variable', '0.51.0')
501    @typed_pos_args('dep.get_variable', optargs=[str])
502    @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'})
503    @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal'])
504    def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Union[str, T.List[str]]:
505        default_varname = args[0]
506        if default_varname is not None:
507            FeatureNew('Positional argument to dep.get_variable()', '0.58.0').use(self.subproject)
508            for k in ['cmake', 'pkgconfig', 'configtool', 'internal']:
509                kwargs.setdefault(k, default_varname)
510        return self.held_object.get_variable(**kwargs)
511
512    @FeatureNew('dep.include_type', '0.52.0')
513    @noPosargs
514    @noKwargs
515    def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
516        return self.held_object.get_include_type()
517
518    @FeatureNew('dep.as_system', '0.52.0')
519    @noKwargs
520    def as_system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency:
521        args = listify(args)
522        new_is_system = 'system'
523        if len(args) > 1:
524            raise InterpreterException('as_system takes only one optional value')
525        if len(args) == 1:
526            if not isinstance(args[0], str):
527                raise InterpreterException('as_system takes exactly one string parameter')
528            new_is_system = args[0]
529        new_dep = self.held_object.generate_system_dependency(new_is_system)
530        return new_dep
531
532    @FeatureNew('dep.as_link_whole', '0.56.0')
533    @noKwargs
534    @noPosargs
535    def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency:
536        if not isinstance(self.held_object, InternalDependency):
537            raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects')
538        new_dep = self.held_object.generate_link_whole_dependency()
539        return new_dep
540
541class ExternalProgramHolder(ObjectHolder[ExternalProgram]):
542    def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None:
543        super().__init__(ep, interpreter)
544        self.methods.update({'found': self.found_method,
545                             'path': self.path_method,
546                             'full_path': self.full_path_method})
547
548    @noPosargs
549    @noKwargs
550    def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
551        return self.found()
552
553    @noPosargs
554    @noKwargs
555    @FeatureDeprecated('ExternalProgram.path', '0.55.0',
556                       'use ExternalProgram.full_path() instead')
557    def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
558        return self._full_path()
559
560    @noPosargs
561    @noKwargs
562    @FeatureNew('ExternalProgram.full_path', '0.55.0')
563    def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
564        return self._full_path()
565
566    def _full_path(self) -> str:
567        if not self.found():
568            raise InterpreterException('Unable to get the path of a not-found external program')
569        path = self.held_object.get_path()
570        assert path is not None
571        return path
572
573    def found(self) -> bool:
574        return self.held_object.found()
575
576class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]):
577    def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'):
578        super().__init__(el, interpreter)
579        self.methods.update({'found': self.found_method,
580                             'type_name': self.type_name_method,
581                             'partial_dependency': self.partial_dependency_method,
582                             })
583
584    @noPosargs
585    @noKwargs
586    def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
587        return self.held_object.type_name
588
589    @noPosargs
590    @noKwargs
591    def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
592        return self.held_object.found()
593
594    @FeatureNew('dep.partial_dependency', '0.46.0')
595    @noPosargs
596    @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS)
597    def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency:
598        pdep = self.held_object.get_partial_dependency(**kwargs)
599        return pdep
600
601# A machine that's statically known from the cross file
602class MachineHolder(ObjectHolder['MachineInfo']):
603    def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'):
604        super().__init__(machine_info, interpreter)
605        self.methods.update({'system': self.system_method,
606                             'cpu': self.cpu_method,
607                             'cpu_family': self.cpu_family_method,
608                             'endian': self.endian_method,
609                             })
610
611    @noPosargs
612    @noKwargs
613    def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
614        return self.held_object.cpu_family
615
616    @noPosargs
617    @noKwargs
618    def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
619        return self.held_object.cpu
620
621    @noPosargs
622    @noKwargs
623    def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
624        return self.held_object.system
625
626    @noPosargs
627    @noKwargs
628    def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
629        return self.held_object.endian
630
631class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]):
632    pass
633
634class FileHolder(ObjectHolder[mesonlib.File]):
635    pass
636
637class HeadersHolder(ObjectHolder[build.Headers]):
638    pass
639
640class DataHolder(ObjectHolder[build.Data]):
641    pass
642
643class InstallDirHolder(ObjectHolder[build.InstallDir]):
644    pass
645
646class ManHolder(ObjectHolder[build.Man]):
647    pass
648
649class EmptyDirHolder(ObjectHolder[build.EmptyDir]):
650    pass
651
652class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]):
653    pass
654
655class Test(MesonInterpreterObject):
656    def __init__(self, name: str, project: str, suite: T.List[str],
657                 exe: T.Union[ExternalProgram, build.Executable, build.CustomTarget],
658                 depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
659                 is_parallel: bool,
660                 cmd_args: T.List[T.Union[str, mesonlib.File, build.Target]],
661                 env: build.EnvironmentVariables,
662                 should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
663                 priority: int):
664        super().__init__()
665        self.name = name
666        self.suite = listify(suite)
667        self.project_name = project
668        self.exe = exe
669        self.depends = depends
670        self.is_parallel = is_parallel
671        self.cmd_args = cmd_args
672        self.env = env
673        self.should_fail = should_fail
674        self.timeout = timeout
675        self.workdir = workdir
676        self.protocol = TestProtocol.from_str(protocol)
677        self.priority = priority
678
679    def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarget]:
680        return self.exe
681
682    def get_name(self) -> str:
683        return self.name
684
685class NullSubprojectInterpreter(HoldableObject):
686    pass
687
688# TODO: This should really be an `ObjectHolder`, but the additional stuff in this
689#       class prevents this. Thus, this class should be split into a pure
690#       `ObjectHolder` and a class specifically for storing in `Interpreter`.
691class SubprojectHolder(MesonInterpreterObject):
692
693    def __init__(self, subinterpreter: T.Union['Interpreter', NullSubprojectInterpreter],
694                 subdir: str,
695                 warnings: int = 0,
696                 disabled_feature: T.Optional[str] = None,
697                 exception: T.Optional[MesonException] = None) -> None:
698        super().__init__()
699        self.held_object = subinterpreter
700        self.warnings = warnings
701        self.disabled_feature = disabled_feature
702        self.exception = exception
703        self.subdir = PurePath(subdir).as_posix()
704        self.methods.update({'get_variable': self.get_variable_method,
705                             'found': self.found_method,
706                             })
707
708    @noPosargs
709    @noKwargs
710    def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
711        return self.found()
712
713    def found(self) -> bool:
714        return not isinstance(self.held_object, NullSubprojectInterpreter)
715
716    @noKwargs
717    @noArgsFlattening
718    @unholder_return
719    def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]:
720        if len(args) < 1 or len(args) > 2:
721            raise InterpreterException('Get_variable takes one or two arguments.')
722        if isinstance(self.held_object, NullSubprojectInterpreter):  # == not self.found()
723            raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir))
724        varname = args[0]
725        if not isinstance(varname, str):
726            raise InterpreterException('Get_variable first argument must be a string.')
727        try:
728            return self.held_object.variables[varname]
729        except KeyError:
730            pass
731
732        if len(args) == 2:
733            return self.held_object._holderify(args[1])
734
735        raise InvalidArguments(f'Requested variable "{varname}" not found.')
736
737class ModuleObjectHolder(ObjectHolder[ModuleObject]):
738    def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var:
739        modobj = self.held_object
740        method = modobj.methods.get(method_name)
741        if not method:
742            raise InvalidCode(f'Unknown method {method_name!r} in object.')
743        if not getattr(method, 'no-args-flattening', False):
744            args = flatten(args)
745        if not getattr(method, 'no-second-level-holder-flattening', False):
746            args, kwargs = resolve_second_level_holders(args, kwargs)
747        state = ModuleState(self.interpreter)
748        # Many modules do for example self.interpreter.find_program_impl(),
749        # so we have to ensure they use the current interpreter and not the one
750        # that first imported that module, otherwise it will use outdated
751        # overrides.
752        if isinstance(modobj, ExtensionModule):
753            modobj.interpreter = self.interpreter
754        ret = method(state, args, kwargs)
755        if isinstance(ret, ModuleReturnValue):
756            self.interpreter.process_new_values(ret.new_objects)
757            ret = ret.return_value
758        return ret
759
760class MutableModuleObjectHolder(ModuleObjectHolder, MutableInterpreterObject):
761    def __deepcopy__(self, memo: T.Dict[int, T.Any]) -> 'MutableModuleObjectHolder':
762        # Deepcopy only held object, not interpreter
763        modobj = copy.deepcopy(self.held_object, memo)
764        return MutableModuleObjectHolder(modobj, self.interpreter)
765
766
767_BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build.BothLibraries])
768
769class BuildTargetHolder(ObjectHolder[_BuildTarget]):
770    def __init__(self, target: _BuildTarget, interp: 'Interpreter'):
771        super().__init__(target, interp)
772        self.methods.update({'extract_objects': self.extract_objects_method,
773                             'extract_all_objects': self.extract_all_objects_method,
774                             'name': self.name_method,
775                             'get_id': self.get_id_method,
776                             'outdir': self.outdir_method,
777                             'full_path': self.full_path_method,
778                             'path': self.path_method,
779                             'found': self.found_method,
780                             'private_dir_include': self.private_dir_include_method,
781                             })
782
783    def __repr__(self) -> str:
784        r = '<{} {}: {}>'
785        h = self.held_object
786        assert isinstance(h, build.BuildTarget)
787        return r.format(self.__class__.__name__, h.get_id(), h.filename)
788
789    @property
790    def _target_object(self) -> build.BuildTarget:
791        if isinstance(self.held_object, build.BothLibraries):
792            return self.held_object.get_default_object()
793        assert isinstance(self.held_object, build.BuildTarget)
794        return self.held_object
795
796    def is_cross(self) -> bool:
797        return not self._target_object.environment.machines.matches_build_machine(self._target_object.for_machine)
798
799    @noPosargs
800    @noKwargs
801    def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
802        if not (isinstance(self.held_object, build.Executable) and self.held_object.was_returned_by_find_program):
803            FeatureNew.single_use('BuildTarget.found', '0.59.0', subproject=self.held_object.subproject)
804        return True
805
806    @noPosargs
807    @noKwargs
808    def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs:
809        return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)])
810
811    @noPosargs
812    @noKwargs
813    def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
814        return self.interpreter.backend.get_target_filename_abs(self._target_object)
815
816    @noPosargs
817    @noKwargs
818    @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead')
819    def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
820        return self.interpreter.backend.get_target_filename_abs(self._target_object)
821
822    @noPosargs
823    @noKwargs
824    def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
825        return self.interpreter.backend.get_target_dir(self._target_object)
826
827    @noKwargs
828    @typed_pos_args('extract_objects', varargs=(mesonlib.File, str))
829    def extract_objects_method(self, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects:
830        return self._target_object.extract_objects(args[0])
831
832    @noPosargs
833    @typed_kwargs(
834        'extract_all_objects',
835        KwargInfo(
836            'recursive', bool, default=False, since='0.46.0',
837            not_set_warning=textwrap.dedent('''\
838                extract_all_objects called without setting recursive
839                keyword argument. Meson currently defaults to
840                non-recursive to maintain backward compatibility but
841                the default will be changed in the future.
842            ''')
843        )
844    )
845    def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects:
846        return self._target_object.extract_all_objects(kwargs['recursive'])
847
848    @noPosargs
849    @noKwargs
850    def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
851        return self._target_object.get_id()
852
853    @FeatureNew('name', '0.54.0')
854    @noPosargs
855    @noKwargs
856    def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
857        return self._target_object.name
858
859class ExecutableHolder(BuildTargetHolder[build.Executable]):
860    pass
861
862class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]):
863    pass
864
865class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]):
866    pass
867
868class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]):
869    def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'):
870        # FIXME: This build target always represents the shared library, but
871        # that should be configurable.
872        super().__init__(libs, interp)
873        self.methods.update({'get_shared_lib': self.get_shared_lib_method,
874                             'get_static_lib': self.get_static_lib_method,
875                             })
876
877    def __repr__(self) -> str:
878        r = '<{} {}: {}, {}: {}>'
879        h1 = self.held_object.shared
880        h2 = self.held_object.static
881        return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
882
883    @noPosargs
884    @noKwargs
885    def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary:
886        return self.held_object.shared
887
888    @noPosargs
889    @noKwargs
890    def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary:
891        return self.held_object.static
892
893class SharedModuleHolder(BuildTargetHolder[build.SharedModule]):
894    pass
895
896class JarHolder(BuildTargetHolder[build.Jar]):
897    pass
898
899class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]):
900    def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'):
901        super().__init__(target, interp)
902        self.methods.update({'full_path': self.full_path_method,
903                             })
904
905    @FeatureNew('custom_target[i].full_path', '0.54.0')
906    @noPosargs
907    @noKwargs
908    def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
909        assert self.interpreter.backend is not None
910        return self.interpreter.backend.get_target_filename_abs(self.held_object)
911
912class CustomTargetHolder(ObjectHolder[build.CustomTarget]):
913    def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'):
914        super().__init__(target, interp)
915        self.methods.update({'full_path': self.full_path_method,
916                             'to_list': self.to_list_method,
917                             })
918
919        self.operators.update({
920            MesonOperator.INDEX: self.op_index,
921        })
922
923    def __repr__(self) -> str:
924        r = '<{} {}: {}>'
925        h = self.held_object
926        return r.format(self.__class__.__name__, h.get_id(), h.command)
927
928    @noPosargs
929    @noKwargs
930    def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
931        return self.interpreter.backend.get_target_filename_abs(self.held_object)
932
933    @FeatureNew('custom_target.to_list', '0.54.0')
934    @noPosargs
935    @noKwargs
936    def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]:
937        result = []
938        for i in self.held_object:
939            result.append(i)
940        return result
941
942    @noKwargs
943    @typed_operator(MesonOperator.INDEX, int)
944    def op_index(self, other: int) -> build.CustomTargetIndex:
945        try:
946            return self.held_object[other]
947        except IndexError:
948            raise InvalidArguments(f'Index {other} out of bounds of custom target {self.held_object.name} output of size {len(self.held_object)}.')
949
950class RunTargetHolder(ObjectHolder[build.RunTarget]):
951    pass
952
953class AliasTargetHolder(ObjectHolder[build.AliasTarget]):
954    pass
955
956class GeneratedListHolder(ObjectHolder[build.GeneratedList]):
957    pass
958
959class GeneratorHolder(ObjectHolder[build.Generator]):
960    def __init__(self, gen: build.Generator, interpreter: 'Interpreter'):
961        super().__init__(gen, interpreter)
962        self.methods.update({'process': self.process_method})
963
964    @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList))
965    @typed_kwargs(
966        'generator.process',
967        KwargInfo('preserve_path_from', (str, NoneType), since='0.45.0'),
968        KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]),
969    )
970    def process_method(self,
971                       args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]],
972                       kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList:
973        preserve_path_from = kwargs['preserve_path_from']
974        if preserve_path_from is not None:
975            preserve_path_from = os.path.normpath(preserve_path_from)
976            if not os.path.isabs(preserve_path_from):
977                # This is a bit of a hack. Fix properly before merging.
978                raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
979
980        if any(isinstance(a, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for a in args[0]):
981            FeatureNew.single_use(
982                'Calling generator.process with CustomTaget or Index of CustomTarget.',
983                '0.57.0', self.interpreter.subproject)
984
985        gl = self.held_object.process_files(args[0], self.interpreter,
986                                            preserve_path_from, extra_args=kwargs['extra_args'])
987
988        return gl
989