1# Copyright 2012-2019 The Meson development team
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5
6#     http://www.apache.org/licenses/LICENSE-2.0
7
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from . import mparser
15from . import environment
16from . import coredata
17from . import dependencies
18from . import mlog
19from . import build
20from . import optinterpreter
21from . import compilers
22from .wrap import wrap, WrapMode
23from . import mesonlib
24from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep, unholder
25from .dependencies import ExternalProgram
26from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
27from .depfile import DepFile
28from .interpreterbase import InterpreterBase
29from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
30from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
31from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound
32from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
33from .interpreterbase import ObjectHolder
34from .modules import ModuleReturnValue
35from .cmake import CMakeInterpreter
36from .backend.backends import TestProtocol
37
38from pathlib import Path, PurePath
39import os
40import shutil
41import uuid
42import re
43import shlex
44import stat
45import subprocess
46import collections
47import functools
48import typing as T
49
50import importlib
51
52permitted_method_kwargs = {
53    'partial_dependency': {'compile_args', 'link_args', 'links', 'includes',
54                           'sources'},
55}
56
57def stringifyUserArguments(args):
58    if isinstance(args, list):
59        return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
60    elif isinstance(args, dict):
61        return '{%s}' % ', '.join(['%s : %s' % (stringifyUserArguments(k), stringifyUserArguments(v)) for k, v in args.items()])
62    elif isinstance(args, int):
63        return str(args)
64    elif isinstance(args, str):
65        return "'%s'" % args
66    raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
67
68
69class OverrideProgram(dependencies.ExternalProgram):
70    pass
71
72
73class FeatureOptionHolder(InterpreterObject, ObjectHolder):
74    def __init__(self, env, name, option):
75        InterpreterObject.__init__(self)
76        ObjectHolder.__init__(self, option)
77        if option.is_auto():
78            self.held_object = env.coredata.builtins['auto_features']
79        self.name = name
80        self.methods.update({'enabled': self.enabled_method,
81                             'disabled': self.disabled_method,
82                             'auto': self.auto_method,
83                             })
84
85    @noPosargs
86    @permittedKwargs({})
87    def enabled_method(self, args, kwargs):
88        return self.held_object.is_enabled()
89
90    @noPosargs
91    @permittedKwargs({})
92    def disabled_method(self, args, kwargs):
93        return self.held_object.is_disabled()
94
95    @noPosargs
96    @permittedKwargs({})
97    def auto_method(self, args, kwargs):
98        return self.held_object.is_auto()
99
100def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True):
101    val = kwargs.get('required', default)
102    disabled = False
103    required = False
104    feature = None
105    if isinstance(val, FeatureOptionHolder):
106        if not feature_check:
107            feature_check = FeatureNew('User option "feature"', '0.47.0')
108        feature_check.use(subproject)
109        option = val.held_object
110        feature = val.name
111        if option.is_disabled():
112            disabled = True
113        elif option.is_enabled():
114            required = True
115    elif isinstance(val, bool):
116        required = val
117    else:
118        raise InterpreterException('required keyword argument must be boolean or a feature option')
119
120    # Keep boolean value in kwargs to simplify other places where this kwarg is
121    # checked.
122    kwargs['required'] = required
123
124    return disabled, required, feature
125
126def extract_search_dirs(kwargs):
127    search_dirs = mesonlib.stringlistify(kwargs.get('dirs', []))
128    search_dirs = [Path(d).expanduser() for d in search_dirs]
129    for d in search_dirs:
130        if mesonlib.is_windows() and d.root.startswith('\\'):
131            # a Unix-path starting with `/` that is not absolute on Windows.
132            # discard without failing for end-user ease of cross-platform directory arrays
133            continue
134        if not d.is_absolute():
135            raise InvalidCode('Search directory {} is not an absolute path.'.format(d))
136    return list(map(str, search_dirs))
137
138class TryRunResultHolder(InterpreterObject):
139    def __init__(self, res):
140        super().__init__()
141        self.res = res
142        self.methods.update({'returncode': self.returncode_method,
143                             'compiled': self.compiled_method,
144                             'stdout': self.stdout_method,
145                             'stderr': self.stderr_method,
146                             })
147
148    @noPosargs
149    @permittedKwargs({})
150    def returncode_method(self, args, kwargs):
151        return self.res.returncode
152
153    @noPosargs
154    @permittedKwargs({})
155    def compiled_method(self, args, kwargs):
156        return self.res.compiled
157
158    @noPosargs
159    @permittedKwargs({})
160    def stdout_method(self, args, kwargs):
161        return self.res.stdout
162
163    @noPosargs
164    @permittedKwargs({})
165    def stderr_method(self, args, kwargs):
166        return self.res.stderr
167
168class RunProcess(InterpreterObject):
169
170    def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True):
171        super().__init__()
172        if not isinstance(cmd, ExternalProgram):
173            raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
174        self.capture = capture
175        pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
176        self.returncode = pc.returncode
177        self.methods.update({'returncode': self.returncode_method,
178                             'stdout': self.stdout_method,
179                             'stderr': self.stderr_method,
180                             })
181
182    def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False):
183        command_array = cmd.get_command() + args
184        menv = {'MESON_SOURCE_ROOT': source_dir,
185                'MESON_BUILD_ROOT': build_dir,
186                'MESON_SUBDIR': subdir,
187                'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]),
188                }
189        if in_builddir:
190            cwd = os.path.join(build_dir, subdir)
191        else:
192            cwd = os.path.join(source_dir, subdir)
193        child_env = os.environ.copy()
194        child_env.update(menv)
195        child_env = env.get_env(child_env)
196        stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
197        mlog.debug('Running command:', ' '.join(command_array))
198        try:
199            p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
200            if self.capture:
201                mlog.debug('--- stdout ---')
202                mlog.debug(o)
203            else:
204                o = ''
205                mlog.debug('--- stdout disabled ---')
206            mlog.debug('--- stderr ---')
207            mlog.debug(e)
208            mlog.debug('')
209
210            if check and p.returncode != 0:
211                raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
212
213            return p, o, e
214        except FileNotFoundError:
215            raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
216
217    @noPosargs
218    @permittedKwargs({})
219    def returncode_method(self, args, kwargs):
220        return self.returncode
221
222    @noPosargs
223    @permittedKwargs({})
224    def stdout_method(self, args, kwargs):
225        return self.stdout
226
227    @noPosargs
228    @permittedKwargs({})
229    def stderr_method(self, args, kwargs):
230        return self.stderr
231
232class ConfigureFileHolder(InterpreterObject, ObjectHolder):
233
234    def __init__(self, subdir, sourcename, targetname, configuration_data):
235        InterpreterObject.__init__(self)
236        obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data)
237        ObjectHolder.__init__(self, obj)
238
239
240class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
241    def __init__(self, initial_values=None):
242        MutableInterpreterObject.__init__(self)
243        ObjectHolder.__init__(self, build.EnvironmentVariables())
244        self.methods.update({'set': self.set_method,
245                             'append': self.append_method,
246                             'prepend': self.prepend_method,
247                             })
248        if isinstance(initial_values, dict):
249            for k, v in initial_values.items():
250                self.set_method([k, v], {})
251        elif isinstance(initial_values, list):
252            for e in initial_values:
253                if '=' not in e:
254                    raise InterpreterException('Env var definition must be of type key=val.')
255                (k, val) = e.split('=', 1)
256                k = k.strip()
257                val = val.strip()
258                if ' ' in k:
259                    raise InterpreterException('Env var key must not have spaces in it.')
260                self.set_method([k, val], {})
261        elif initial_values:
262            raise AssertionError('Unsupported EnvironmentVariablesHolder initial_values')
263
264    def __repr__(self):
265        repr_str = "<{0}: {1}>"
266        return repr_str.format(self.__class__.__name__, self.held_object.envvars)
267
268    def add_var(self, method, args, kwargs):
269        if not isinstance(kwargs.get("separator", ""), str):
270            raise InterpreterException("EnvironmentVariablesHolder methods 'separator'"
271                                       " argument needs to be a string.")
272        if len(args) < 2:
273            raise InterpreterException("EnvironmentVariablesHolder methods require at least"
274                                       "2 arguments, first is the name of the variable and"
275                                       " following one are values")
276        # Warn when someone tries to use append() or prepend() on an env var
277        # which already has an operation set on it. People seem to think that
278        # multiple append/prepend operations stack, but they don't.
279        if method != self.held_object.set and self.held_object.has_name(args[0]):
280            mlog.warning('Overriding previous value of environment variable {!r} with a new one'
281                         .format(args[0]), location=self.current_node)
282        self.held_object.add_var(method, args[0], args[1:], kwargs)
283
284    @stringArgs
285    @permittedKwargs({'separator'})
286    def set_method(self, args, kwargs):
287        self.add_var(self.held_object.set, args, kwargs)
288
289    @stringArgs
290    @permittedKwargs({'separator'})
291    def append_method(self, args, kwargs):
292        self.add_var(self.held_object.append, args, kwargs)
293
294    @stringArgs
295    @permittedKwargs({'separator'})
296    def prepend_method(self, args, kwargs):
297        self.add_var(self.held_object.prepend, args, kwargs)
298
299
300class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder):
301    def __init__(self, pv, initial_values=None):
302        MutableInterpreterObject.__init__(self)
303        self.used = False # These objects become immutable after use in configure_file.
304        ObjectHolder.__init__(self, build.ConfigurationData(), pv)
305        self.methods.update({'set': self.set_method,
306                             'set10': self.set10_method,
307                             'set_quoted': self.set_quoted_method,
308                             'has': self.has_method,
309                             'get': self.get_method,
310                             'get_unquoted': self.get_unquoted_method,
311                             'merge_from': self.merge_from_method,
312                             })
313        if isinstance(initial_values, dict):
314            for k, v in initial_values.items():
315                self.set_method([k, v], {})
316        elif initial_values:
317            raise AssertionError('Unsupported ConfigurationDataHolder initial_values')
318
319    def is_used(self):
320        return self.used
321
322    def mark_used(self):
323        self.used = True
324
325    def validate_args(self, args, kwargs):
326        if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
327            mlog.deprecation('Passing a list as the single argument to '
328                             'configuration_data.set is deprecated. This will '
329                             'become a hard error in the future.',
330                             location=self.current_node)
331            args = args[0]
332
333        if len(args) != 2:
334            raise InterpreterException("Configuration set requires 2 arguments.")
335        if self.used:
336            raise InterpreterException("Can not set values on configuration object that has been used.")
337        name, val = args
338        if not isinstance(val, (int, str)):
339            msg = 'Setting a configuration data value to {!r} is invalid, ' \
340                  'and will fail at configure_file(). If you are using it ' \
341                  'just to store some values, please use a dict instead.'
342            mlog.deprecation(msg.format(val), location=self.current_node)
343        desc = kwargs.get('description', None)
344        if not isinstance(name, str):
345            raise InterpreterException("First argument to set must be a string.")
346        if desc is not None and not isinstance(desc, str):
347            raise InterpreterException('Description must be a string.')
348
349        return name, val, desc
350
351    @noArgsFlattening
352    def set_method(self, args, kwargs):
353        (name, val, desc) = self.validate_args(args, kwargs)
354        self.held_object.values[name] = (val, desc)
355
356    def set_quoted_method(self, args, kwargs):
357        (name, val, desc) = self.validate_args(args, kwargs)
358        if not isinstance(val, str):
359            raise InterpreterException("Second argument to set_quoted must be a string.")
360        escaped_val = '\\"'.join(val.split('"'))
361        self.held_object.values[name] = ('"' + escaped_val + '"', desc)
362
363    def set10_method(self, args, kwargs):
364        (name, val, desc) = self.validate_args(args, kwargs)
365        if val:
366            self.held_object.values[name] = (1, desc)
367        else:
368            self.held_object.values[name] = (0, desc)
369
370    def has_method(self, args, kwargs):
371        return args[0] in self.held_object.values
372
373    @FeatureNew('configuration_data.get()', '0.38.0')
374    @noArgsFlattening
375    def get_method(self, args, kwargs):
376        if len(args) < 1 or len(args) > 2:
377            raise InterpreterException('Get method takes one or two arguments.')
378        name = args[0]
379        if name in self.held_object:
380            return self.held_object.get(name)[0]
381        if len(args) > 1:
382            return args[1]
383        raise InterpreterException('Entry %s not in configuration data.' % name)
384
385    @FeatureNew('configuration_data.get_unquoted()', '0.44.0')
386    def get_unquoted_method(self, args, kwargs):
387        if len(args) < 1 or len(args) > 2:
388            raise InterpreterException('Get method takes one or two arguments.')
389        name = args[0]
390        if name in self.held_object:
391            val = self.held_object.get(name)[0]
392        elif len(args) > 1:
393            val = args[1]
394        else:
395            raise InterpreterException('Entry %s not in configuration data.' % name)
396        if val[0] == '"' and val[-1] == '"':
397            return val[1:-1]
398        return val
399
400    def get(self, name):
401        return self.held_object.values[name] # (val, desc)
402
403    def keys(self):
404        return self.held_object.values.keys()
405
406    def merge_from_method(self, args, kwargs):
407        if len(args) != 1:
408            raise InterpreterException('Merge_from takes one positional argument.')
409        from_object = args[0]
410        if not isinstance(from_object, ConfigurationDataHolder):
411            raise InterpreterException('Merge_from argument must be a configuration data object.')
412        from_object = from_object.held_object
413        for k, v in from_object.values.items():
414            self.held_object.values[k] = v
415
416# Interpreter objects can not be pickled so we must have
417# these wrappers.
418
419class DependencyHolder(InterpreterObject, ObjectHolder):
420    def __init__(self, dep, pv):
421        InterpreterObject.__init__(self)
422        ObjectHolder.__init__(self, dep, pv)
423        self.methods.update({'found': self.found_method,
424                             'type_name': self.type_name_method,
425                             'version': self.version_method,
426                             'name': self.name_method,
427                             'get_pkgconfig_variable': self.pkgconfig_method,
428                             'get_configtool_variable': self.configtool_method,
429                             'get_variable': self.variable_method,
430                             'partial_dependency': self.partial_dependency_method,
431                             'include_type': self.include_type_method,
432                             'as_system': self.as_system_method,
433                             })
434
435    def found(self):
436        return self.found_method([], {})
437
438    @noPosargs
439    @permittedKwargs({})
440    def type_name_method(self, args, kwargs):
441        return self.held_object.type_name
442
443    @noPosargs
444    @permittedKwargs({})
445    def found_method(self, args, kwargs):
446        if self.held_object.type_name == 'internal':
447            return True
448        return self.held_object.found()
449
450    @noPosargs
451    @permittedKwargs({})
452    def version_method(self, args, kwargs):
453        return self.held_object.get_version()
454
455    @noPosargs
456    @permittedKwargs({})
457    def name_method(self, args, kwargs):
458        return self.held_object.get_name()
459
460    @permittedKwargs({'define_variable', 'default'})
461    def pkgconfig_method(self, args, kwargs):
462        args = listify(args)
463        if len(args) != 1:
464            raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
465        varname = args[0]
466        if not isinstance(varname, str):
467            raise InterpreterException('Variable name must be a string.')
468        return self.held_object.get_pkgconfig_variable(varname, kwargs)
469
470    @FeatureNew('dep.get_configtool_variable', '0.44.0')
471    @permittedKwargs({})
472    def configtool_method(self, args, kwargs):
473        args = listify(args)
474        if len(args) != 1:
475            raise InterpreterException('get_configtool_variable takes exactly one argument.')
476        varname = args[0]
477        if not isinstance(varname, str):
478            raise InterpreterException('Variable name must be a string.')
479        return self.held_object.get_configtool_variable(varname)
480
481    @FeatureNew('dep.partial_dependency', '0.46.0')
482    @noPosargs
483    @permittedKwargs(permitted_method_kwargs['partial_dependency'])
484    def partial_dependency_method(self, args, kwargs):
485        pdep = self.held_object.get_partial_dependency(**kwargs)
486        return DependencyHolder(pdep, self.subproject)
487
488    @FeatureNew('dep.get_variable', '0.51.0')
489    @noPosargs
490    @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'})
491    @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal'])
492    def variable_method(self, args, kwargs):
493        return self.held_object.get_variable(**kwargs)
494
495    @FeatureNew('dep.include_type', '0.52.0')
496    @noPosargs
497    @permittedKwargs({})
498    def include_type_method(self, args, kwargs):
499        return self.held_object.get_include_type()
500
501    @FeatureNew('dep.as_system', '0.52.0')
502    @permittedKwargs({})
503    def as_system_method(self, args, kwargs):
504        args = listify(args)
505        new_is_system = 'system'
506        if len(args) > 1:
507            raise InterpreterException('as_system takes only one optional value')
508        if len(args) == 1:
509            new_is_system = args[0]
510        new_dep = self.held_object.generate_system_dependency(new_is_system)
511        return DependencyHolder(new_dep, self.subproject)
512
513class ExternalProgramHolder(InterpreterObject, ObjectHolder):
514    def __init__(self, ep, subproject, backend=None):
515        InterpreterObject.__init__(self)
516        ObjectHolder.__init__(self, ep)
517        self.subproject = subproject
518        self.backend = backend
519        self.methods.update({'found': self.found_method,
520                             'path': self.path_method,
521                             'full_path': self.full_path_method})
522        self.cached_version = None
523
524    @noPosargs
525    @permittedKwargs({})
526    def found_method(self, args, kwargs):
527        return self.found()
528
529    @noPosargs
530    @permittedKwargs({})
531    @FeatureDeprecated('ExternalProgram.path', '0.55.0',
532                       'use ExternalProgram.full_path() instead')
533    def path_method(self, args, kwargs):
534        return self._full_path()
535
536    @noPosargs
537    @permittedKwargs({})
538    @FeatureNew('ExternalProgram.full_path', '0.55.0')
539    def full_path_method(self, args, kwargs):
540        return self._full_path()
541
542    def _full_path(self):
543        exe = self.held_object
544        if isinstance(exe, build.Executable):
545            return self.backend.get_target_filename_abs(exe)
546        return exe.get_path()
547
548    def found(self):
549        return isinstance(self.held_object, build.Executable) or self.held_object.found()
550
551    def get_command(self):
552        return self.held_object.get_command()
553
554    def get_name(self):
555        exe = self.held_object
556        if isinstance(exe, build.Executable):
557            return exe.name
558        return exe.get_name()
559
560    def get_version(self, interpreter):
561        if isinstance(self.held_object, build.Executable):
562            return self.held_object.project_version
563        if not self.cached_version:
564            raw_cmd = self.get_command() + ['--version']
565            cmd = [self, '--version']
566            res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True)
567            if res.returncode != 0:
568                m = 'Running {!r} failed'
569                raise InterpreterException(m.format(raw_cmd))
570            output = res.stdout.strip()
571            if not output:
572                output = res.stderr.strip()
573            match = re.search(r'([0-9][0-9\.]+)', output)
574            if not match:
575                m = 'Could not find a version number in output of {!r}'
576                raise InterpreterException(m.format(raw_cmd))
577            self.cached_version = match.group(1)
578        return self.cached_version
579
580class ExternalLibraryHolder(InterpreterObject, ObjectHolder):
581    def __init__(self, el, pv):
582        InterpreterObject.__init__(self)
583        ObjectHolder.__init__(self, el, pv)
584        self.methods.update({'found': self.found_method,
585                             'type_name': self.type_name_method,
586                             'partial_dependency': self.partial_dependency_method,
587                             })
588
589    def found(self):
590        return self.held_object.found()
591
592    @noPosargs
593    @permittedKwargs({})
594    def type_name_method(self, args, kwargs):
595        return self.held_object.type_name
596
597    @noPosargs
598    @permittedKwargs({})
599    def found_method(self, args, kwargs):
600        return self.found()
601
602    def get_name(self):
603        return self.held_object.name
604
605    def get_compile_args(self):
606        return self.held_object.get_compile_args()
607
608    def get_link_args(self):
609        return self.held_object.get_link_args()
610
611    def get_exe_args(self):
612        return self.held_object.get_exe_args()
613
614    @FeatureNew('dep.partial_dependency', '0.46.0')
615    @noPosargs
616    @permittedKwargs(permitted_method_kwargs['partial_dependency'])
617    def partial_dependency_method(self, args, kwargs):
618        pdep = self.held_object.get_partial_dependency(**kwargs)
619        return DependencyHolder(pdep, self.subproject)
620
621class GeneratorHolder(InterpreterObject, ObjectHolder):
622    @FeatureNewKwargs('generator', '0.43.0', ['capture'])
623    def __init__(self, interp, args, kwargs):
624        self.interpreter = interp
625        InterpreterObject.__init__(self)
626        ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject)
627        self.methods.update({'process': self.process_method})
628
629    @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
630    @permittedKwargs({'extra_args', 'preserve_path_from'})
631    def process_method(self, args, kwargs):
632        extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
633        if 'preserve_path_from' in kwargs:
634            preserve_path_from = kwargs['preserve_path_from']
635            if not isinstance(preserve_path_from, str):
636                raise InvalidArguments('Preserve_path_from must be a string.')
637            preserve_path_from = os.path.normpath(preserve_path_from)
638            if not os.path.isabs(preserve_path_from):
639                # This is a bit of a hack. Fix properly before merging.
640                raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
641        else:
642            preserve_path_from = None
643        gl = self.held_object.process_files('Generator', args, self.interpreter,
644                                            preserve_path_from, extra_args=extras)
645        return GeneratedListHolder(gl)
646
647
648class GeneratedListHolder(InterpreterObject, ObjectHolder):
649    def __init__(self, arg1, extra_args=None):
650        InterpreterObject.__init__(self)
651        if isinstance(arg1, GeneratorHolder):
652            ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else []))
653        else:
654            ObjectHolder.__init__(self, arg1)
655
656    def __repr__(self):
657        r = '<{}: {!r}>'
658        return r.format(self.__class__.__name__, self.held_object.get_outputs())
659
660    def add_file(self, a):
661        self.held_object.add_file(a)
662
663# A machine that's statically known from the cross file
664class MachineHolder(InterpreterObject, ObjectHolder):
665    def __init__(self, machine_info):
666        InterpreterObject.__init__(self)
667        ObjectHolder.__init__(self, machine_info)
668        self.methods.update({'system': self.system_method,
669                             'cpu': self.cpu_method,
670                             'cpu_family': self.cpu_family_method,
671                             'endian': self.endian_method,
672                             })
673
674    @noPosargs
675    @permittedKwargs({})
676    def cpu_family_method(self, args, kwargs):
677        return self.held_object.cpu_family
678
679    @noPosargs
680    @permittedKwargs({})
681    def cpu_method(self, args, kwargs):
682        return self.held_object.cpu
683
684    @noPosargs
685    @permittedKwargs({})
686    def system_method(self, args, kwargs):
687        return self.held_object.system
688
689    @noPosargs
690    @permittedKwargs({})
691    def endian_method(self, args, kwargs):
692        return self.held_object.endian
693
694class IncludeDirsHolder(InterpreterObject, ObjectHolder):
695    def __init__(self, idobj):
696        InterpreterObject.__init__(self)
697        ObjectHolder.__init__(self, idobj)
698
699class Headers(InterpreterObject):
700
701    def __init__(self, sources, kwargs):
702        InterpreterObject.__init__(self)
703        self.sources = sources
704        self.install_subdir = kwargs.get('subdir', '')
705        if os.path.isabs(self.install_subdir):
706            mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.')
707        self.custom_install_dir = kwargs.get('install_dir', None)
708        self.custom_install_mode = kwargs.get('install_mode', None)
709        if self.custom_install_dir is not None:
710            if not isinstance(self.custom_install_dir, str):
711                raise InterpreterException('Custom_install_dir must be a string.')
712
713    def set_install_subdir(self, subdir):
714        self.install_subdir = subdir
715
716    def get_install_subdir(self):
717        return self.install_subdir
718
719    def get_sources(self):
720        return self.sources
721
722    def get_custom_install_dir(self):
723        return self.custom_install_dir
724
725    def get_custom_install_mode(self):
726        return self.custom_install_mode
727
728class DataHolder(InterpreterObject, ObjectHolder):
729    def __init__(self, data):
730        InterpreterObject.__init__(self)
731        ObjectHolder.__init__(self, data)
732
733    def get_source_subdir(self):
734        return self.held_object.source_subdir
735
736    def get_sources(self):
737        return self.held_object.sources
738
739    def get_install_dir(self):
740        return self.held_object.install_dir
741
742class InstallDir(InterpreterObject):
743    def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory):
744        InterpreterObject.__init__(self)
745        self.source_subdir = src_subdir
746        self.installable_subdir = inst_subdir
747        self.install_dir = install_dir
748        self.install_mode = install_mode
749        self.exclude = exclude
750        self.strip_directory = strip_directory
751
752class Man(InterpreterObject):
753
754    def __init__(self, sources, kwargs):
755        InterpreterObject.__init__(self)
756        self.sources = sources
757        self.validate_sources()
758        self.custom_install_dir = kwargs.get('install_dir', None)
759        self.custom_install_mode = kwargs.get('install_mode', None)
760        if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str):
761            raise InterpreterException('Custom_install_dir must be a string.')
762
763    def validate_sources(self):
764        for s in self.sources:
765            try:
766                num = int(s.split('.')[-1])
767            except (IndexError, ValueError):
768                num = 0
769            if num < 1 or num > 8:
770                raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
771
772    def get_custom_install_dir(self):
773        return self.custom_install_dir
774
775    def get_custom_install_mode(self):
776        return self.custom_install_mode
777
778    def get_sources(self):
779        return self.sources
780
781class GeneratedObjectsHolder(InterpreterObject, ObjectHolder):
782    def __init__(self, held_object):
783        InterpreterObject.__init__(self)
784        ObjectHolder.__init__(self, held_object)
785
786class TargetHolder(InterpreterObject, ObjectHolder):
787    def __init__(self, target, interp):
788        InterpreterObject.__init__(self)
789        ObjectHolder.__init__(self, target, interp.subproject)
790        self.interpreter = interp
791
792class BuildTargetHolder(TargetHolder):
793    def __init__(self, target, interp):
794        super().__init__(target, interp)
795        self.methods.update({'extract_objects': self.extract_objects_method,
796                             'extract_all_objects': self.extract_all_objects_method,
797                             'name': self.name_method,
798                             'get_id': self.get_id_method,
799                             'outdir': self.outdir_method,
800                             'full_path': self.full_path_method,
801                             'private_dir_include': self.private_dir_include_method,
802                             })
803
804    def __repr__(self):
805        r = '<{} {}: {}>'
806        h = self.held_object
807        return r.format(self.__class__.__name__, h.get_id(), h.filename)
808
809    def is_cross(self):
810        return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine)
811
812    @noPosargs
813    @permittedKwargs({})
814    def private_dir_include_method(self, args, kwargs):
815        return IncludeDirsHolder(build.IncludeDirs('', [], False,
816                                                   [self.interpreter.backend.get_target_private_dir(self.held_object)]))
817
818    @noPosargs
819    @permittedKwargs({})
820    def full_path_method(self, args, kwargs):
821        return self.interpreter.backend.get_target_filename_abs(self.held_object)
822
823    @noPosargs
824    @permittedKwargs({})
825    def outdir_method(self, args, kwargs):
826        return self.interpreter.backend.get_target_dir(self.held_object)
827
828    @permittedKwargs({})
829    def extract_objects_method(self, args, kwargs):
830        gobjs = self.held_object.extract_objects(args)
831        return GeneratedObjectsHolder(gobjs)
832
833    @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive'])
834    @noPosargs
835    @permittedKwargs({'recursive'})
836    def extract_all_objects_method(self, args, kwargs):
837        recursive = kwargs.get('recursive', False)
838        gobjs = self.held_object.extract_all_objects(recursive)
839        if gobjs.objlist and 'recursive' not in kwargs:
840            mlog.warning('extract_all_objects called without setting recursive '
841                         'keyword argument. Meson currently defaults to '
842                         'non-recursive to maintain backward compatibility but '
843                         'the default will be changed in the future.',
844                         location=self.current_node)
845        return GeneratedObjectsHolder(gobjs)
846
847    @noPosargs
848    @permittedKwargs({})
849    def get_id_method(self, args, kwargs):
850        return self.held_object.get_id()
851
852    @FeatureNew('name', '0.54.0')
853    @noPosargs
854    @permittedKwargs({})
855    def name_method(self, args, kwargs):
856        return self.held_object.name
857
858class ExecutableHolder(BuildTargetHolder):
859    def __init__(self, target, interp):
860        super().__init__(target, interp)
861
862class StaticLibraryHolder(BuildTargetHolder):
863    def __init__(self, target, interp):
864        super().__init__(target, interp)
865
866class SharedLibraryHolder(BuildTargetHolder):
867    def __init__(self, target, interp):
868        super().__init__(target, interp)
869        # Set to True only when called from self.func_shared_lib().
870        target.shared_library_only = False
871
872class BothLibrariesHolder(BuildTargetHolder):
873    def __init__(self, shared_holder, static_holder, interp):
874        # FIXME: This build target always represents the shared library, but
875        # that should be configurable.
876        super().__init__(shared_holder.held_object, interp)
877        self.shared_holder = shared_holder
878        self.static_holder = static_holder
879        self.methods.update({'get_shared_lib': self.get_shared_lib_method,
880                             'get_static_lib': self.get_static_lib_method,
881                             })
882
883    def __repr__(self):
884        r = '<{} {}: {}, {}: {}>'
885        h1 = self.shared_holder.held_object
886        h2 = self.static_holder.held_object
887        return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
888
889    @noPosargs
890    @permittedKwargs({})
891    def get_shared_lib_method(self, args, kwargs):
892        return self.shared_holder
893
894    @noPosargs
895    @permittedKwargs({})
896    def get_static_lib_method(self, args, kwargs):
897        return self.static_holder
898
899class SharedModuleHolder(BuildTargetHolder):
900    def __init__(self, target, interp):
901        super().__init__(target, interp)
902
903class JarHolder(BuildTargetHolder):
904    def __init__(self, target, interp):
905        super().__init__(target, interp)
906
907class CustomTargetIndexHolder(TargetHolder):
908    def __init__(self, target, interp):
909        super().__init__(target, interp)
910        self.methods.update({'full_path': self.full_path_method,
911                             })
912
913    @FeatureNew('custom_target[i].full_path', '0.54.0')
914    @noPosargs
915    @permittedKwargs({})
916    def full_path_method(self, args, kwargs):
917        return self.interpreter.backend.get_target_filename_abs(self.held_object)
918
919class CustomTargetHolder(TargetHolder):
920    def __init__(self, target, interp):
921        super().__init__(target, interp)
922        self.methods.update({'full_path': self.full_path_method,
923                             'to_list': self.to_list_method,
924                             })
925
926    def __repr__(self):
927        r = '<{} {}: {}>'
928        h = self.held_object
929        return r.format(self.__class__.__name__, h.get_id(), h.command)
930
931    @noPosargs
932    @permittedKwargs({})
933    def full_path_method(self, args, kwargs):
934        return self.interpreter.backend.get_target_filename_abs(self.held_object)
935
936    @FeatureNew('custom_target.to_list', '0.54.0')
937    @noPosargs
938    @permittedKwargs({})
939    def to_list_method(self, args, kwargs):
940        result = []
941        for i in self.held_object:
942            result.append(CustomTargetIndexHolder(i, self.interpreter))
943        return result
944
945    def __getitem__(self, index):
946        return CustomTargetIndexHolder(self.held_object[index], self.interpreter)
947
948    def __setitem__(self, index, value):  # lgtm[py/unexpected-raise-in-special-method]
949        raise InterpreterException('Cannot set a member of a CustomTarget')
950
951    def __delitem__(self, index):  # lgtm[py/unexpected-raise-in-special-method]
952        raise InterpreterException('Cannot delete a member of a CustomTarget')
953
954    def outdir_include(self):
955        return IncludeDirsHolder(build.IncludeDirs('', [], False,
956                                                   [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))]))
957
958class RunTargetHolder(TargetHolder):
959    def __init__(self, target, interp):
960        super().__init__(target, interp)
961
962    def __repr__(self):
963        r = '<{} {}: {}>'
964        h = self.held_object
965        return r.format(self.__class__.__name__, h.get_id(), h.command)
966
967class Test(InterpreterObject):
968    def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable,
969                 depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
970                 is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables,
971                 should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
972                 priority: int):
973        InterpreterObject.__init__(self)
974        self.name = name
975        self.suite = suite
976        self.project_name = project
977        self.exe = exe
978        self.depends = depends
979        self.is_parallel = is_parallel
980        self.cmd_args = cmd_args
981        self.env = env
982        self.should_fail = should_fail
983        self.timeout = timeout
984        self.workdir = workdir
985        self.protocol = TestProtocol.from_str(protocol)
986        self.priority = priority
987
988    def get_exe(self):
989        return self.exe
990
991    def get_name(self):
992        return self.name
993
994class SubprojectHolder(InterpreterObject, ObjectHolder):
995
996    def __init__(self, subinterpreter, subproject_dir, name, warnings=0, disabled_feature=None,
997                 exception=None):
998        InterpreterObject.__init__(self)
999        ObjectHolder.__init__(self, subinterpreter)
1000        self.name = name
1001        self.warnings = warnings
1002        self.disabled_feature = disabled_feature
1003        self.exception = exception
1004        self.subproject_dir = subproject_dir
1005        self.methods.update({'get_variable': self.get_variable_method,
1006                             'found': self.found_method,
1007                             })
1008
1009    @noPosargs
1010    @permittedKwargs({})
1011    def found_method(self, args, kwargs):
1012        return self.found()
1013
1014    def found(self):
1015        return self.held_object is not None
1016
1017    @permittedKwargs({})
1018    @noArgsFlattening
1019    def get_variable_method(self, args, kwargs):
1020        if len(args) < 1 or len(args) > 2:
1021            raise InterpreterException('Get_variable takes one or two arguments.')
1022        if not self.found():
1023            raise InterpreterException('Subproject "%s/%s" disabled can\'t get_variable on it.' % (
1024                self.subproject_dir, self.name))
1025        varname = args[0]
1026        if not isinstance(varname, str):
1027            raise InterpreterException('Get_variable first argument must be a string.')
1028        try:
1029            return self.held_object.variables[varname]
1030        except KeyError:
1031            pass
1032
1033        if len(args) == 2:
1034            return args[1]
1035
1036        raise InvalidArguments('Requested variable "{0}" not found.'.format(varname))
1037
1038header_permitted_kwargs = set([
1039    'required',
1040    'prefix',
1041    'no_builtin_args',
1042    'include_directories',
1043    'args',
1044    'dependencies',
1045])
1046
1047find_library_permitted_kwargs = set([
1048    'has_headers',
1049    'required',
1050    'dirs',
1051    'static',
1052])
1053
1054find_library_permitted_kwargs |= set(['header_' + k for k in header_permitted_kwargs])
1055
1056class CompilerHolder(InterpreterObject):
1057    def __init__(self, compiler, env, subproject):
1058        InterpreterObject.__init__(self)
1059        self.compiler = compiler
1060        self.environment = env
1061        self.subproject = subproject
1062        self.methods.update({'compiles': self.compiles_method,
1063                             'links': self.links_method,
1064                             'get_id': self.get_id_method,
1065                             'get_linker_id': self.get_linker_id_method,
1066                             'compute_int': self.compute_int_method,
1067                             'sizeof': self.sizeof_method,
1068                             'get_define': self.get_define_method,
1069                             'check_header': self.check_header_method,
1070                             'has_header': self.has_header_method,
1071                             'has_header_symbol': self.has_header_symbol_method,
1072                             'run': self.run_method,
1073                             'has_function': self.has_function_method,
1074                             'has_member': self.has_member_method,
1075                             'has_members': self.has_members_method,
1076                             'has_type': self.has_type_method,
1077                             'alignment': self.alignment_method,
1078                             'version': self.version_method,
1079                             'cmd_array': self.cmd_array_method,
1080                             'find_library': self.find_library_method,
1081                             'has_argument': self.has_argument_method,
1082                             'has_function_attribute': self.has_func_attribute_method,
1083                             'get_supported_function_attributes': self.get_supported_function_attributes_method,
1084                             'has_multi_arguments': self.has_multi_arguments_method,
1085                             'get_supported_arguments': self.get_supported_arguments_method,
1086                             'first_supported_argument': self.first_supported_argument_method,
1087                             'has_link_argument': self.has_link_argument_method,
1088                             'has_multi_link_arguments': self.has_multi_link_arguments_method,
1089                             'get_supported_link_arguments': self.get_supported_link_arguments_method,
1090                             'first_supported_link_argument': self.first_supported_link_argument_method,
1091                             'unittest_args': self.unittest_args_method,
1092                             'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method,
1093                             'get_argument_syntax': self.get_argument_syntax_method,
1094                             })
1095
1096    def _dep_msg(self, deps, endl):
1097        msg_single = 'with dependency {}'
1098        msg_many = 'with dependencies {}'
1099        if not deps:
1100            return endl
1101        if endl is None:
1102            endl = ''
1103        tpl = msg_many if len(deps) > 1 else msg_single
1104        names = []
1105        for d in deps:
1106            if isinstance(d, dependencies.ExternalLibrary):
1107                name = '-l' + d.name
1108            else:
1109                name = d.name
1110            names.append(name)
1111        return tpl.format(', '.join(names)) + endl
1112
1113    @noPosargs
1114    @permittedKwargs({})
1115    def version_method(self, args, kwargs):
1116        return self.compiler.version
1117
1118    @noPosargs
1119    @permittedKwargs({})
1120    def cmd_array_method(self, args, kwargs):
1121        return self.compiler.exelist
1122
1123    def determine_args(self, kwargs, mode='link'):
1124        nobuiltins = kwargs.get('no_builtin_args', False)
1125        if not isinstance(nobuiltins, bool):
1126            raise InterpreterException('Type of no_builtin_args not a boolean.')
1127        args = []
1128        incdirs = extract_as_list(kwargs, 'include_directories')
1129        for i in incdirs:
1130            if not isinstance(i, IncludeDirsHolder):
1131                raise InterpreterException('Include directories argument must be an include_directories object.')
1132            for idir in i.held_object.get_incdirs():
1133                idir = os.path.join(self.environment.get_source_dir(),
1134                                    i.held_object.get_curdir(), idir)
1135                args += self.compiler.get_include_args(idir, False)
1136        if not nobuiltins:
1137            for_machine = Interpreter.machine_from_native_kwarg(kwargs)
1138            opts = self.environment.coredata.compiler_options[for_machine][self.compiler.language]
1139            args += self.compiler.get_option_compile_args(opts)
1140            if mode == 'link':
1141                args += self.compiler.get_option_link_args(opts)
1142        args += mesonlib.stringlistify(kwargs.get('args', []))
1143        return args
1144
1145    def determine_dependencies(self, kwargs, endl=':'):
1146        deps = kwargs.get('dependencies', None)
1147        if deps is not None:
1148            deps = listify(deps)
1149            final_deps = []
1150            for d in deps:
1151                try:
1152                    d = d.held_object
1153                except Exception:
1154                    pass
1155                if isinstance(d, InternalDependency) or not isinstance(d, Dependency):
1156                    raise InterpreterException('Dependencies must be external dependencies')
1157                final_deps.append(d)
1158            deps = final_deps
1159        return deps, self._dep_msg(deps, endl)
1160
1161    @permittedKwargs({
1162        'prefix',
1163        'args',
1164        'dependencies',
1165    })
1166    def alignment_method(self, args, kwargs):
1167        if len(args) != 1:
1168            raise InterpreterException('Alignment method takes exactly one positional argument.')
1169        check_stringlist(args)
1170        typename = args[0]
1171        prefix = kwargs.get('prefix', '')
1172        if not isinstance(prefix, str):
1173            raise InterpreterException('Prefix argument of alignment must be a string.')
1174        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
1175        deps, msg = self.determine_dependencies(kwargs)
1176        result = self.compiler.alignment(typename, prefix, self.environment,
1177                                         extra_args=extra_args,
1178                                         dependencies=deps)
1179        mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result)
1180        return result
1181
1182    @permittedKwargs({
1183        'name',
1184        'no_builtin_args',
1185        'include_directories',
1186        'args',
1187        'dependencies',
1188    })
1189    def run_method(self, args, kwargs):
1190        if len(args) != 1:
1191            raise InterpreterException('Run method takes exactly one positional argument.')
1192        code = args[0]
1193        if isinstance(code, mesonlib.File):
1194            code = mesonlib.File.from_absolute_file(
1195                code.rel_to_builddir(self.environment.source_dir))
1196        elif not isinstance(code, str):
1197            raise InvalidArguments('Argument must be string or file.')
1198        testname = kwargs.get('name', '')
1199        if not isinstance(testname, str):
1200            raise InterpreterException('Testname argument must be a string.')
1201        extra_args = functools.partial(self.determine_args, kwargs)
1202        deps, msg = self.determine_dependencies(kwargs, endl=None)
1203        result = self.compiler.run(code, self.environment, extra_args=extra_args,
1204                                   dependencies=deps)
1205        if len(testname) > 0:
1206            if not result.compiled:
1207                h = mlog.red('DID NOT COMPILE')
1208            elif result.returncode == 0:
1209                h = mlog.green('YES')
1210            else:
1211                h = mlog.red('NO (%d)' % result.returncode)
1212            mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h)
1213        return TryRunResultHolder(result)
1214
1215    @noPosargs
1216    @permittedKwargs({})
1217    def get_id_method(self, args, kwargs):
1218        return self.compiler.get_id()
1219
1220    @noPosargs
1221    @permittedKwargs({})
1222    @FeatureNew('compiler.get_linker_id', '0.53.0')
1223    def get_linker_id_method(self, args, kwargs):
1224        return self.compiler.get_linker_id()
1225
1226    @noPosargs
1227    @permittedKwargs({})
1228    def symbols_have_underscore_prefix_method(self, args, kwargs):
1229        '''
1230        Check if the compiler prefixes _ (underscore) to global C symbols
1231        See: https://en.wikipedia.org/wiki/Name_mangling#C
1232        '''
1233        return self.compiler.symbols_have_underscore_prefix(self.environment)
1234
1235    @noPosargs
1236    @permittedKwargs({})
1237    def unittest_args_method(self, args, kwargs):
1238        '''
1239        This function is deprecated and should not be used.
1240        It can be removed in a future version of Meson.
1241        '''
1242        if not hasattr(self.compiler, 'get_feature_args'):
1243            raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language()))
1244        build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir())
1245        return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src)
1246
1247    @permittedKwargs({
1248        'prefix',
1249        'no_builtin_args',
1250        'include_directories',
1251        'args',
1252        'dependencies',
1253    })
1254    def has_member_method(self, args, kwargs):
1255        if len(args) != 2:
1256            raise InterpreterException('Has_member takes exactly two arguments.')
1257        check_stringlist(args)
1258        typename, membername = args
1259        prefix = kwargs.get('prefix', '')
1260        if not isinstance(prefix, str):
1261            raise InterpreterException('Prefix argument of has_member must be a string.')
1262        extra_args = functools.partial(self.determine_args, kwargs)
1263        deps, msg = self.determine_dependencies(kwargs)
1264        had, cached = self.compiler.has_members(typename, [membername], prefix,
1265                                                self.environment,
1266                                                extra_args=extra_args,
1267                                                dependencies=deps)
1268        cached = mlog.blue('(cached)') if cached else ''
1269        if had:
1270            hadtxt = mlog.green('YES')
1271        else:
1272            hadtxt = mlog.red('NO')
1273        mlog.log('Checking whether type', mlog.bold(typename, True),
1274                 'has member', mlog.bold(membername, True), msg, hadtxt, cached)
1275        return had
1276
1277    @permittedKwargs({
1278        'prefix',
1279        'no_builtin_args',
1280        'include_directories',
1281        'args',
1282        'dependencies',
1283    })
1284    def has_members_method(self, args, kwargs):
1285        if len(args) < 2:
1286            raise InterpreterException('Has_members needs at least two arguments.')
1287        check_stringlist(args)
1288        typename, *membernames = args
1289        prefix = kwargs.get('prefix', '')
1290        if not isinstance(prefix, str):
1291            raise InterpreterException('Prefix argument of has_members must be a string.')
1292        extra_args = functools.partial(self.determine_args, kwargs)
1293        deps, msg = self.determine_dependencies(kwargs)
1294        had, cached = self.compiler.has_members(typename, membernames, prefix,
1295                                                self.environment,
1296                                                extra_args=extra_args,
1297                                                dependencies=deps)
1298        cached = mlog.blue('(cached)') if cached else ''
1299        if had:
1300            hadtxt = mlog.green('YES')
1301        else:
1302            hadtxt = mlog.red('NO')
1303        members = mlog.bold(', '.join(['"{}"'.format(m) for m in membernames]))
1304        mlog.log('Checking whether type', mlog.bold(typename, True),
1305                 'has members', members, msg, hadtxt, cached)
1306        return had
1307
1308    @permittedKwargs({
1309        'prefix',
1310        'no_builtin_args',
1311        'include_directories',
1312        'args',
1313        'dependencies',
1314    })
1315    def has_function_method(self, args, kwargs):
1316        if len(args) != 1:
1317            raise InterpreterException('Has_function takes exactly one argument.')
1318        check_stringlist(args)
1319        funcname = args[0]
1320        prefix = kwargs.get('prefix', '')
1321        if not isinstance(prefix, str):
1322            raise InterpreterException('Prefix argument of has_function must be a string.')
1323        extra_args = self.determine_args(kwargs)
1324        deps, msg = self.determine_dependencies(kwargs)
1325        had, cached = self.compiler.has_function(funcname, prefix, self.environment,
1326                                                 extra_args=extra_args,
1327                                                 dependencies=deps)
1328        cached = mlog.blue('(cached)') if cached else ''
1329        if had:
1330            hadtxt = mlog.green('YES')
1331        else:
1332            hadtxt = mlog.red('NO')
1333        mlog.log('Checking for function', mlog.bold(funcname, True), msg, hadtxt, cached)
1334        return had
1335
1336    @permittedKwargs({
1337        'prefix',
1338        'no_builtin_args',
1339        'include_directories',
1340        'args',
1341        'dependencies',
1342    })
1343    def has_type_method(self, args, kwargs):
1344        if len(args) != 1:
1345            raise InterpreterException('Has_type takes exactly one argument.')
1346        check_stringlist(args)
1347        typename = args[0]
1348        prefix = kwargs.get('prefix', '')
1349        if not isinstance(prefix, str):
1350            raise InterpreterException('Prefix argument of has_type must be a string.')
1351        extra_args = functools.partial(self.determine_args, kwargs)
1352        deps, msg = self.determine_dependencies(kwargs)
1353        had, cached = self.compiler.has_type(typename, prefix, self.environment,
1354                                             extra_args=extra_args, dependencies=deps)
1355        cached = mlog.blue('(cached)') if cached else ''
1356        if had:
1357            hadtxt = mlog.green('YES')
1358        else:
1359            hadtxt = mlog.red('NO')
1360        mlog.log('Checking for type', mlog.bold(typename, True), msg, hadtxt, cached)
1361        return had
1362
1363    @FeatureNew('compiler.compute_int', '0.40.0')
1364    @permittedKwargs({
1365        'prefix',
1366        'low',
1367        'high',
1368        'guess',
1369        'no_builtin_args',
1370        'include_directories',
1371        'args',
1372        'dependencies',
1373    })
1374    def compute_int_method(self, args, kwargs):
1375        if len(args) != 1:
1376            raise InterpreterException('Compute_int takes exactly one argument.')
1377        check_stringlist(args)
1378        expression = args[0]
1379        prefix = kwargs.get('prefix', '')
1380        low = kwargs.get('low', None)
1381        high = kwargs.get('high', None)
1382        guess = kwargs.get('guess', None)
1383        if not isinstance(prefix, str):
1384            raise InterpreterException('Prefix argument of compute_int must be a string.')
1385        if low is not None and not isinstance(low, int):
1386            raise InterpreterException('Low argument of compute_int must be an int.')
1387        if high is not None and not isinstance(high, int):
1388            raise InterpreterException('High argument of compute_int must be an int.')
1389        if guess is not None and not isinstance(guess, int):
1390            raise InterpreterException('Guess argument of compute_int must be an int.')
1391        extra_args = functools.partial(self.determine_args, kwargs)
1392        deps, msg = self.determine_dependencies(kwargs)
1393        res = self.compiler.compute_int(expression, low, high, guess, prefix,
1394                                        self.environment, extra_args=extra_args,
1395                                        dependencies=deps)
1396        mlog.log('Computing int of', mlog.bold(expression, True), msg, res)
1397        return res
1398
1399    @permittedKwargs({
1400        'prefix',
1401        'no_builtin_args',
1402        'include_directories',
1403        'args',
1404        'dependencies',
1405    })
1406    def sizeof_method(self, args, kwargs):
1407        if len(args) != 1:
1408            raise InterpreterException('Sizeof takes exactly one argument.')
1409        check_stringlist(args)
1410        element = args[0]
1411        prefix = kwargs.get('prefix', '')
1412        if not isinstance(prefix, str):
1413            raise InterpreterException('Prefix argument of sizeof must be a string.')
1414        extra_args = functools.partial(self.determine_args, kwargs)
1415        deps, msg = self.determine_dependencies(kwargs)
1416        esize = self.compiler.sizeof(element, prefix, self.environment,
1417                                     extra_args=extra_args, dependencies=deps)
1418        mlog.log('Checking for size of', mlog.bold(element, True), msg, esize)
1419        return esize
1420
1421    @FeatureNew('compiler.get_define', '0.40.0')
1422    @permittedKwargs({
1423        'prefix',
1424        'no_builtin_args',
1425        'include_directories',
1426        'args',
1427        'dependencies',
1428    })
1429    def get_define_method(self, args, kwargs):
1430        if len(args) != 1:
1431            raise InterpreterException('get_define() takes exactly one argument.')
1432        check_stringlist(args)
1433        element = args[0]
1434        prefix = kwargs.get('prefix', '')
1435        if not isinstance(prefix, str):
1436            raise InterpreterException('Prefix argument of get_define() must be a string.')
1437        extra_args = functools.partial(self.determine_args, kwargs)
1438        deps, msg = self.determine_dependencies(kwargs)
1439        value, cached = self.compiler.get_define(element, prefix, self.environment,
1440                                                 extra_args=extra_args,
1441                                                 dependencies=deps)
1442        cached = mlog.blue('(cached)') if cached else ''
1443        mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached)
1444        return value
1445
1446    @permittedKwargs({
1447        'name',
1448        'no_builtin_args',
1449        'include_directories',
1450        'args',
1451        'dependencies',
1452    })
1453    def compiles_method(self, args, kwargs):
1454        if len(args) != 1:
1455            raise InterpreterException('compiles method takes exactly one argument.')
1456        code = args[0]
1457        if isinstance(code, mesonlib.File):
1458            code = mesonlib.File.from_absolute_file(
1459                code.rel_to_builddir(self.environment.source_dir))
1460        elif not isinstance(code, str):
1461            raise InvalidArguments('Argument must be string or file.')
1462        testname = kwargs.get('name', '')
1463        if not isinstance(testname, str):
1464            raise InterpreterException('Testname argument must be a string.')
1465        extra_args = functools.partial(self.determine_args, kwargs)
1466        deps, msg = self.determine_dependencies(kwargs, endl=None)
1467        result, cached = self.compiler.compiles(code, self.environment,
1468                                                extra_args=extra_args,
1469                                                dependencies=deps)
1470        if len(testname) > 0:
1471            if result:
1472                h = mlog.green('YES')
1473            else:
1474                h = mlog.red('NO')
1475            cached = mlog.blue('(cached)') if cached else ''
1476            mlog.log('Checking if', mlog.bold(testname, True), msg, 'compiles:', h, cached)
1477        return result
1478
1479    @permittedKwargs({
1480        'name',
1481        'no_builtin_args',
1482        'include_directories',
1483        'args',
1484        'dependencies',
1485    })
1486    def links_method(self, args, kwargs):
1487        if len(args) != 1:
1488            raise InterpreterException('links method takes exactly one argument.')
1489        code = args[0]
1490        if isinstance(code, mesonlib.File):
1491            code = mesonlib.File.from_absolute_file(
1492                code.rel_to_builddir(self.environment.source_dir))
1493        elif not isinstance(code, str):
1494            raise InvalidArguments('Argument must be string or file.')
1495        testname = kwargs.get('name', '')
1496        if not isinstance(testname, str):
1497            raise InterpreterException('Testname argument must be a string.')
1498        extra_args = functools.partial(self.determine_args, kwargs)
1499        deps, msg = self.determine_dependencies(kwargs, endl=None)
1500        result, cached = self.compiler.links(code, self.environment,
1501                                             extra_args=extra_args,
1502                                             dependencies=deps)
1503        cached = mlog.blue('(cached)') if cached else ''
1504        if len(testname) > 0:
1505            if result:
1506                h = mlog.green('YES')
1507            else:
1508                h = mlog.red('NO')
1509            mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached)
1510        return result
1511
1512    @FeatureNew('compiler.check_header', '0.47.0')
1513    @FeatureNewKwargs('compiler.check_header', '0.50.0', ['required'])
1514    @permittedKwargs(header_permitted_kwargs)
1515    def check_header_method(self, args, kwargs):
1516        if len(args) != 1:
1517            raise InterpreterException('check_header method takes exactly one argument.')
1518        check_stringlist(args)
1519        hname = args[0]
1520        prefix = kwargs.get('prefix', '')
1521        if not isinstance(prefix, str):
1522            raise InterpreterException('Prefix argument of has_header must be a string.')
1523        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
1524        if disabled:
1525            mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
1526            return False
1527        extra_args = functools.partial(self.determine_args, kwargs)
1528        deps, msg = self.determine_dependencies(kwargs)
1529        haz, cached = self.compiler.check_header(hname, prefix, self.environment,
1530                                                 extra_args=extra_args,
1531                                                 dependencies=deps)
1532        cached = mlog.blue('(cached)') if cached else ''
1533        if required and not haz:
1534            raise InterpreterException('{} header {!r} not usable'.format(self.compiler.get_display_language(), hname))
1535        elif haz:
1536            h = mlog.green('YES')
1537        else:
1538            h = mlog.red('NO')
1539        mlog.log('Check usable header', mlog.bold(hname, True), msg, h, cached)
1540        return haz
1541
1542    @FeatureNewKwargs('compiler.has_header', '0.50.0', ['required'])
1543    @permittedKwargs(header_permitted_kwargs)
1544    def has_header_method(self, args, kwargs):
1545        if len(args) != 1:
1546            raise InterpreterException('has_header method takes exactly one argument.')
1547        check_stringlist(args)
1548        hname = args[0]
1549        prefix = kwargs.get('prefix', '')
1550        if not isinstance(prefix, str):
1551            raise InterpreterException('Prefix argument of has_header must be a string.')
1552        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
1553        if disabled:
1554            mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
1555            return False
1556        extra_args = functools.partial(self.determine_args, kwargs)
1557        deps, msg = self.determine_dependencies(kwargs)
1558        haz, cached = self.compiler.has_header(hname, prefix, self.environment,
1559                                               extra_args=extra_args, dependencies=deps)
1560        cached = mlog.blue('(cached)') if cached else ''
1561        if required and not haz:
1562            raise InterpreterException('{} header {!r} not found'.format(self.compiler.get_display_language(), hname))
1563        elif haz:
1564            h = mlog.green('YES')
1565        else:
1566            h = mlog.red('NO')
1567        mlog.log('Has header', mlog.bold(hname, True), msg, h, cached)
1568        return haz
1569
1570    @FeatureNewKwargs('compiler.has_header_symbol', '0.50.0', ['required'])
1571    @permittedKwargs(header_permitted_kwargs)
1572    def has_header_symbol_method(self, args, kwargs):
1573        if len(args) != 2:
1574            raise InterpreterException('has_header_symbol method takes exactly two arguments.')
1575        check_stringlist(args)
1576        hname, symbol = args
1577        prefix = kwargs.get('prefix', '')
1578        if not isinstance(prefix, str):
1579            raise InterpreterException('Prefix argument of has_header_symbol must be a string.')
1580        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
1581        if disabled:
1582            mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled')
1583            return False
1584        extra_args = functools.partial(self.determine_args, kwargs)
1585        deps, msg = self.determine_dependencies(kwargs)
1586        haz, cached = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment,
1587                                                      extra_args=extra_args,
1588                                                      dependencies=deps)
1589        if required and not haz:
1590            raise InterpreterException('{} symbol {} not found in header {}'.format(self.compiler.get_display_language(), symbol, hname))
1591        elif haz:
1592            h = mlog.green('YES')
1593        else:
1594            h = mlog.red('NO')
1595        cached = mlog.blue('(cached)') if cached else ''
1596        mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), msg, h, cached)
1597        return haz
1598
1599    def notfound_library(self, libname):
1600        lib = dependencies.ExternalLibrary(libname, None,
1601                                           self.environment,
1602                                           self.compiler.language,
1603                                           silent=True)
1604        return ExternalLibraryHolder(lib, self.subproject)
1605
1606    @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static'])
1607    @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers'])
1608    @FeatureNewKwargs('compiler.find_library', '0.49.0', ['disabler'])
1609    @disablerIfNotFound
1610    @permittedKwargs(find_library_permitted_kwargs)
1611    def find_library_method(self, args, kwargs):
1612        # TODO add dependencies support?
1613        if len(args) != 1:
1614            raise InterpreterException('find_library method takes one argument.')
1615        libname = args[0]
1616        if not isinstance(libname, str):
1617            raise InterpreterException('Library name not a string.')
1618
1619        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
1620        if disabled:
1621            mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled')
1622            return self.notfound_library(libname)
1623
1624        has_header_kwargs = {k[7:]: v for k, v in kwargs.items() if k.startswith('header_')}
1625        has_header_kwargs['required'] = required
1626        headers = mesonlib.stringlistify(kwargs.get('has_headers', []))
1627        for h in headers:
1628            if not self.has_header_method([h], has_header_kwargs):
1629                return self.notfound_library(libname)
1630
1631        search_dirs = extract_search_dirs(kwargs)
1632
1633        libtype = mesonlib.LibType.PREFER_SHARED
1634        if 'static' in kwargs:
1635            if not isinstance(kwargs['static'], bool):
1636                raise InterpreterException('static must be a boolean')
1637            libtype = mesonlib.LibType.STATIC if kwargs['static'] else mesonlib.LibType.SHARED
1638        linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype)
1639        if required and not linkargs:
1640            if libtype == mesonlib.LibType.PREFER_SHARED:
1641                libtype = 'shared or static'
1642            else:
1643                libtype = libtype.name.lower()
1644            raise InterpreterException('{} {} library {!r} not found'
1645                                       .format(self.compiler.get_display_language(),
1646                                               libtype, libname))
1647        lib = dependencies.ExternalLibrary(libname, linkargs, self.environment,
1648                                           self.compiler.language)
1649        return ExternalLibraryHolder(lib, self.subproject)
1650
1651    @permittedKwargs({})
1652    def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool:
1653        args = mesonlib.stringlistify(args)
1654        if len(args) != 1:
1655            raise InterpreterException('has_argument takes exactly one argument.')
1656        return self.has_multi_arguments_method(args, kwargs)
1657
1658    @permittedKwargs({})
1659    def has_multi_arguments_method(self, args: T.Sequence[str], kwargs: dict):
1660        args = mesonlib.stringlistify(args)
1661        result, cached = self.compiler.has_multi_arguments(args, self.environment)
1662        if result:
1663            h = mlog.green('YES')
1664        else:
1665            h = mlog.red('NO')
1666        cached = mlog.blue('(cached)') if cached else ''
1667        mlog.log(
1668            'Compiler for {} supports arguments {}:'.format(
1669                self.compiler.get_display_language(), ' '.join(args)),
1670            h, cached)
1671        return result
1672
1673    @FeatureNew('compiler.get_supported_arguments', '0.43.0')
1674    @permittedKwargs({})
1675    def get_supported_arguments_method(self, args, kwargs):
1676        args = mesonlib.stringlistify(args)
1677        supported_args = []
1678        for arg in args:
1679            if self.has_argument_method(arg, kwargs):
1680                supported_args.append(arg)
1681        return supported_args
1682
1683    @permittedKwargs({})
1684    def first_supported_argument_method(self, args: T.Sequence[str], kwargs: dict) -> T.List[str]:
1685        for arg in mesonlib.stringlistify(args):
1686            if self.has_argument_method(arg, kwargs):
1687                mlog.log('First supported argument:', mlog.bold(arg))
1688                return [arg]
1689        mlog.log('First supported argument:', mlog.red('None'))
1690        return []
1691
1692    @FeatureNew('compiler.has_link_argument', '0.46.0')
1693    @permittedKwargs({})
1694    def has_link_argument_method(self, args, kwargs):
1695        args = mesonlib.stringlistify(args)
1696        if len(args) != 1:
1697            raise InterpreterException('has_link_argument takes exactly one argument.')
1698        return self.has_multi_link_arguments_method(args, kwargs)
1699
1700    @FeatureNew('compiler.has_multi_link_argument', '0.46.0')
1701    @permittedKwargs({})
1702    def has_multi_link_arguments_method(self, args, kwargs):
1703        args = mesonlib.stringlistify(args)
1704        result, cached = self.compiler.has_multi_link_arguments(args, self.environment)
1705        cached = mlog.blue('(cached)') if cached else ''
1706        if result:
1707            h = mlog.green('YES')
1708        else:
1709            h = mlog.red('NO')
1710        mlog.log(
1711            'Compiler for {} supports link arguments {}:'.format(
1712                self.compiler.get_display_language(), ' '.join(args)),
1713            h, cached)
1714        return result
1715
1716    @FeatureNew('compiler.get_supported_link_arguments_method', '0.46.0')
1717    @permittedKwargs({})
1718    def get_supported_link_arguments_method(self, args, kwargs):
1719        args = mesonlib.stringlistify(args)
1720        supported_args = []
1721        for arg in args:
1722            if self.has_link_argument_method(arg, kwargs):
1723                supported_args.append(arg)
1724        return supported_args
1725
1726    @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0')
1727    @permittedKwargs({})
1728    def first_supported_link_argument_method(self, args, kwargs):
1729        for i in mesonlib.stringlistify(args):
1730            if self.has_link_argument_method(i, kwargs):
1731                mlog.log('First supported link argument:', mlog.bold(i))
1732                return [i]
1733        mlog.log('First supported link argument:', mlog.red('None'))
1734        return []
1735
1736    @FeatureNew('compiler.has_function_attribute', '0.48.0')
1737    @permittedKwargs({})
1738    def has_func_attribute_method(self, args, kwargs):
1739        args = mesonlib.stringlistify(args)
1740        if len(args) != 1:
1741            raise InterpreterException('has_func_attribute takes exactly one argument.')
1742        result, cached = self.compiler.has_func_attribute(args[0], self.environment)
1743        cached = mlog.blue('(cached)') if cached else ''
1744        h = mlog.green('YES') if result else mlog.red('NO')
1745        mlog.log('Compiler for {} supports function attribute {}:'.format(self.compiler.get_display_language(), args[0]), h, cached)
1746        return result
1747
1748    @FeatureNew('compiler.get_supported_function_attributes', '0.48.0')
1749    @permittedKwargs({})
1750    def get_supported_function_attributes_method(self, args, kwargs):
1751        args = mesonlib.stringlistify(args)
1752        return [a for a in args if self.has_func_attribute_method(a, kwargs)]
1753
1754    @FeatureNew('compiler.get_argument_syntax_method', '0.49.0')
1755    @noPosargs
1756    @noKwargs
1757    def get_argument_syntax_method(self, args, kwargs):
1758        return self.compiler.get_argument_syntax()
1759
1760
1761ModuleState = collections.namedtuple('ModuleState', [
1762    'source_root', 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment',
1763    'project_name', 'project_version', 'backend', 'targets',
1764    'data', 'headers', 'man', 'global_args', 'project_args', 'build_machine',
1765    'host_machine', 'target_machine', 'current_node'])
1766
1767class ModuleHolder(InterpreterObject, ObjectHolder):
1768    def __init__(self, modname, module, interpreter):
1769        InterpreterObject.__init__(self)
1770        ObjectHolder.__init__(self, module)
1771        self.modname = modname
1772        self.interpreter = interpreter
1773
1774    def method_call(self, method_name, args, kwargs):
1775        try:
1776            fn = getattr(self.held_object, method_name)
1777        except AttributeError:
1778            raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name))
1779        if method_name.startswith('_'):
1780            raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname))
1781        if not getattr(fn, 'no-args-flattening', False):
1782            args = flatten(args)
1783        # This is not 100% reliable but we can't use hash()
1784        # because the Build object contains dicts and lists.
1785        num_targets = len(self.interpreter.build.targets)
1786        state = ModuleState(
1787            source_root = self.interpreter.environment.get_source_dir(),
1788            build_to_src=mesonlib.relpath(self.interpreter.environment.get_source_dir(),
1789                                          self.interpreter.environment.get_build_dir()),
1790            subproject=self.interpreter.subproject,
1791            subdir=self.interpreter.subdir,
1792            current_lineno=self.interpreter.current_lineno,
1793            environment=self.interpreter.environment,
1794            project_name=self.interpreter.build.project_name,
1795            project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname],
1796            # The backend object is under-used right now, but we will need it:
1797            # https://github.com/mesonbuild/meson/issues/1419
1798            backend=self.interpreter.backend,
1799            targets=self.interpreter.build.targets,
1800            data=self.interpreter.build.data,
1801            headers=self.interpreter.build.get_headers(),
1802            man=self.interpreter.build.get_man(),
1803            #global_args_for_build = self.interpreter.build.global_args.build,
1804            global_args = self.interpreter.build.global_args.host,
1805            #project_args_for_build = self.interpreter.build.projects_args.build.get(self.interpreter.subproject, {}),
1806            project_args = self.interpreter.build.projects_args.host.get(self.interpreter.subproject, {}),
1807            build_machine=self.interpreter.builtin['build_machine'].held_object,
1808            host_machine=self.interpreter.builtin['host_machine'].held_object,
1809            target_machine=self.interpreter.builtin['target_machine'].held_object,
1810            current_node=self.current_node
1811        )
1812        # Many modules do for example self.interpreter.find_program_impl(),
1813        # so we have to ensure they use the current interpreter and not the one
1814        # that first imported that module, otherwise it will use outdated
1815        # overrides.
1816        self.held_object.interpreter = self.interpreter
1817        if self.held_object.is_snippet(method_name):
1818            value = fn(self.interpreter, state, args, kwargs)
1819            return self.interpreter.holderify(value)
1820        else:
1821            value = fn(state, args, kwargs)
1822            if num_targets != len(self.interpreter.build.targets):
1823                raise InterpreterException('Extension module altered internal state illegally.')
1824            return self.interpreter.module_method_callback(value)
1825
1826
1827class Summary:
1828    def __init__(self, project_name, project_version):
1829        self.project_name = project_name
1830        self.project_version = project_version
1831        self.sections = collections.defaultdict(dict)
1832        self.max_key_len = 0
1833
1834    def add_section(self, section, values, kwargs):
1835        bool_yn = kwargs.get('bool_yn', False)
1836        if not isinstance(bool_yn, bool):
1837            raise InterpreterException('bool_yn keyword argument must be boolean')
1838        list_sep = kwargs.get('list_sep')
1839        if list_sep is not None and not isinstance(list_sep, str):
1840            raise InterpreterException('list_sep keyword argument must be string')
1841        for k, v in values.items():
1842            if k in self.sections[section]:
1843                raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k))
1844            formatted_values = []
1845            for i in listify(v):
1846                if not isinstance(i, (str, int)):
1847                    m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean'
1848                    raise InterpreterException(m.format(section, k))
1849                if bool_yn and isinstance(i, bool):
1850                    formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
1851                else:
1852                    formatted_values.append(i)
1853            self.sections[section][k] = (formatted_values, list_sep)
1854            self.max_key_len = max(self.max_key_len, len(k))
1855
1856    def dump(self):
1857        mlog.log(self.project_name, mlog.normal_cyan(self.project_version))
1858        for section, values in self.sections.items():
1859            mlog.log('')  # newline
1860            if section:
1861                mlog.log(' ', mlog.bold(section))
1862            for k, v in values.items():
1863                v, list_sep = v
1864                indent = self.max_key_len - len(k) + 3
1865                end = ' ' if v else ''
1866                mlog.log(' ' * indent, k + ':', end=end)
1867                if list_sep is None:
1868                    indent = self.max_key_len + 6
1869                    list_sep = '\n' + ' ' * indent
1870                mlog.log(*v, sep=list_sep)
1871        mlog.log('')  # newline
1872
1873
1874class MesonMain(InterpreterObject):
1875    def __init__(self, build, interpreter):
1876        InterpreterObject.__init__(self)
1877        self.build = build
1878        self.interpreter = interpreter
1879        self._found_source_scripts = {}
1880        self.methods.update({'get_compiler': self.get_compiler_method,
1881                             'is_cross_build': self.is_cross_build_method,
1882                             'has_exe_wrapper': self.has_exe_wrapper_method,
1883                             'can_run_host_binaries': self.can_run_host_binaries_method,
1884                             'is_unity': self.is_unity_method,
1885                             'is_subproject': self.is_subproject_method,
1886                             'current_source_dir': self.current_source_dir_method,
1887                             'current_build_dir': self.current_build_dir_method,
1888                             'source_root': self.source_root_method,
1889                             'build_root': self.build_root_method,
1890                             'add_install_script': self.add_install_script_method,
1891                             'add_postconf_script': self.add_postconf_script_method,
1892                             'add_dist_script': self.add_dist_script_method,
1893                             'install_dependency_manifest': self.install_dependency_manifest_method,
1894                             'override_dependency': self.override_dependency_method,
1895                             'override_find_program': self.override_find_program_method,
1896                             'project_version': self.project_version_method,
1897                             'project_license': self.project_license_method,
1898                             'version': self.version_method,
1899                             'project_name': self.project_name_method,
1900                             'get_cross_property': self.get_cross_property_method,
1901                             'get_external_property': self.get_external_property_method,
1902                             'backend': self.backend_method,
1903                             })
1904
1905    def _find_source_script(self, prog: T.Union[str, ExecutableHolder], args):
1906        if isinstance(prog, ExecutableHolder):
1907            prog_path = self.interpreter.backend.get_target_filename(prog.held_object)
1908            return build.RunScript([prog_path], args)
1909        elif isinstance(prog, ExternalProgramHolder):
1910            return build.RunScript(prog.get_command(), args)
1911
1912        # Prefer scripts in the current source directory
1913        search_dir = os.path.join(self.interpreter.environment.source_dir,
1914                                  self.interpreter.subdir)
1915        key = (prog, search_dir)
1916        if key in self._found_source_scripts:
1917            found = self._found_source_scripts[key]
1918        else:
1919            found = dependencies.ExternalProgram(prog, search_dir=search_dir)
1920            if found.found():
1921                self._found_source_scripts[key] = found
1922            else:
1923                m = 'Script or command {!r} not found or not executable'
1924                raise InterpreterException(m.format(prog))
1925        return build.RunScript(found.get_command(), args)
1926
1927    def _process_script_args(
1928            self, name: str, args: T.List[T.Union[
1929                str, mesonlib.File, CustomTargetHolder,
1930                CustomTargetIndexHolder, ConfigureFileHolder,
1931                ExternalProgramHolder, ExecutableHolder,
1932            ]], allow_built: bool = False) -> T.List[str]:
1933        script_args = []  # T.List[str]
1934        new = False
1935        for a in args:
1936            a = unholder(a)
1937            if isinstance(a, str):
1938                script_args.append(a)
1939            elif isinstance(a, mesonlib.File):
1940                new = True
1941                script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir))
1942            elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)):
1943                if not allow_built:
1944                    raise InterpreterException('Arguments to {} cannot be built'.format(name))
1945                new = True
1946                script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()])
1947
1948                # This feels really hacky, but I'm not sure how else to fix
1949                # this without completely rewriting install script handling.
1950                # This is complicated by the fact that the install target
1951                # depends on all.
1952                if isinstance(a, build.CustomTargetIndex):
1953                    a.target.build_by_default = True
1954                else:
1955                    a.build_by_default = True
1956            elif isinstance(a, build.ConfigureFile):
1957                new = True
1958                script_args.append(os.path.join(a.subdir, a.targetname))
1959            elif isinstance(a, dependencies.ExternalProgram):
1960                script_args.extend(a.command)
1961                new = True
1962            else:
1963                raise InterpreterException(
1964                    'Arguments to {} must be strings, Files, CustomTargets, '
1965                    'Indexes of CustomTargets, or ConfigureFiles'.format(name))
1966        if new:
1967            FeatureNew.single_use(
1968                'Calling "{}" with File, CustomTaget, Index of CustomTarget, '
1969                'ConfigureFile, Executable, or ExternalProgram'.format(name),
1970                '0.55.0', self.interpreter.subproject)
1971        return script_args
1972
1973    @permittedKwargs(set())
1974    def add_install_script_method(self, args: 'T.Tuple[T.Union[str, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder], ...]', kwargs):
1975        if len(args) < 1:
1976            raise InterpreterException('add_install_script takes one or more arguments')
1977        script_args = self._process_script_args('add_install_script', args[1:], allow_built=True)
1978        script = self._find_source_script(args[0], script_args)
1979        self.build.install_scripts.append(script)
1980
1981    @permittedKwargs(set())
1982    def add_postconf_script_method(self, args, kwargs):
1983        if len(args) < 1:
1984            raise InterpreterException('add_postconf_script takes one or more arguments')
1985        script_args = self._process_script_args('add_postconf_script', args[1:], allow_built=True)
1986        script = self._find_source_script(args[0], script_args)
1987        self.build.postconf_scripts.append(script)
1988
1989    @permittedKwargs(set())
1990    def add_dist_script_method(self, args, kwargs):
1991        if len(args) < 1:
1992            raise InterpreterException('add_dist_script takes one or more arguments')
1993        if len(args) > 1:
1994            FeatureNew.single_use('Calling "add_dist_script" with multiple arguments',
1995                                  '0.49.0', self.interpreter.subproject)
1996        if self.interpreter.subproject != '':
1997            raise InterpreterException('add_dist_script may not be used in a subproject.')
1998        script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True)
1999        script = self._find_source_script(args[0], script_args)
2000        self.build.dist_scripts.append(script)
2001
2002    @noPosargs
2003    @permittedKwargs({})
2004    def current_source_dir_method(self, args, kwargs):
2005        src = self.interpreter.environment.source_dir
2006        sub = self.interpreter.subdir
2007        if sub == '':
2008            return src
2009        return os.path.join(src, sub)
2010
2011    @noPosargs
2012    @permittedKwargs({})
2013    def current_build_dir_method(self, args, kwargs):
2014        src = self.interpreter.environment.build_dir
2015        sub = self.interpreter.subdir
2016        if sub == '':
2017            return src
2018        return os.path.join(src, sub)
2019
2020    @noPosargs
2021    @permittedKwargs({})
2022    def backend_method(self, args, kwargs):
2023        return self.interpreter.backend.name
2024
2025    @noPosargs
2026    @permittedKwargs({})
2027    def source_root_method(self, args, kwargs):
2028        return self.interpreter.environment.source_dir
2029
2030    @noPosargs
2031    @permittedKwargs({})
2032    def build_root_method(self, args, kwargs):
2033        return self.interpreter.environment.build_dir
2034
2035    @noPosargs
2036    @permittedKwargs({})
2037    @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.')
2038    def has_exe_wrapper_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool:
2039        return self.can_run_host_binaries_impl(args, kwargs)
2040
2041    @noPosargs
2042    @permittedKwargs({})
2043    @FeatureNew('meson.can_run_host_binaries', '0.55.0')
2044    def can_run_host_binaries_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool:
2045        return self.can_run_host_binaries_impl(args, kwargs)
2046
2047    def can_run_host_binaries_impl(self, args, kwargs):
2048        if (self.is_cross_build_method(None, None) and
2049                self.build.environment.need_exe_wrapper()):
2050            if self.build.environment.exe_wrapper is None:
2051                return False
2052        # We return True when exe_wrap is defined, when it's not needed, and
2053        # when we're compiling natively. The last two are semantically confusing.
2054        # Need to revisit this.
2055        return True
2056
2057    @noPosargs
2058    @permittedKwargs({})
2059    def is_cross_build_method(self, args, kwargs):
2060        return self.build.environment.is_cross_build()
2061
2062    @permittedKwargs({'native'})
2063    def get_compiler_method(self, args, kwargs):
2064        if len(args) != 1:
2065            raise InterpreterException('get_compiler_method must have one and only one argument.')
2066        cname = args[0]
2067        for_machine = Interpreter.machine_from_native_kwarg(kwargs)
2068        clist = self.interpreter.coredata.compilers[for_machine]
2069        if cname in clist:
2070            return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject)
2071        raise InterpreterException('Tried to access compiler for unspecified language "%s".' % cname)
2072
2073    @noPosargs
2074    @permittedKwargs({})
2075    def is_unity_method(self, args, kwargs):
2076        optval = self.interpreter.environment.coredata.get_builtin_option('unity')
2077        if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()):
2078            return True
2079        return False
2080
2081    @noPosargs
2082    @permittedKwargs({})
2083    def is_subproject_method(self, args, kwargs):
2084        return self.interpreter.is_subproject()
2085
2086    @permittedKwargs({})
2087    def install_dependency_manifest_method(self, args, kwargs):
2088        if len(args) != 1:
2089            raise InterpreterException('Must specify manifest install file name')
2090        if not isinstance(args[0], str):
2091            raise InterpreterException('Argument must be a string.')
2092        self.build.dep_manifest_name = args[0]
2093
2094    @FeatureNew('meson.override_find_program', '0.46.0')
2095    @permittedKwargs({})
2096    def override_find_program_method(self, args, kwargs):
2097        if len(args) != 2:
2098            raise InterpreterException('Override needs two arguments')
2099        name, exe = args
2100        if not isinstance(name, str):
2101            raise InterpreterException('First argument must be a string')
2102        if hasattr(exe, 'held_object'):
2103            exe = exe.held_object
2104        if isinstance(exe, mesonlib.File):
2105            abspath = exe.absolute_path(self.interpreter.environment.source_dir,
2106                                        self.interpreter.environment.build_dir)
2107            if not os.path.exists(abspath):
2108                raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
2109            exe = OverrideProgram(abspath)
2110        if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)):
2111            raise InterpreterException('Second argument must be an external program or executable.')
2112        self.interpreter.add_find_program_override(name, exe)
2113
2114    @FeatureNew('meson.override_dependency', '0.54.0')
2115    @permittedKwargs({'native'})
2116    def override_dependency_method(self, args, kwargs):
2117        if len(args) != 2:
2118            raise InterpreterException('Override needs two arguments')
2119        name = args[0]
2120        dep = args[1]
2121        if not isinstance(name, str) or not name:
2122            raise InterpreterException('First argument must be a string and cannot be empty')
2123        if hasattr(dep, 'held_object'):
2124            dep = dep.held_object
2125        if not isinstance(dep, dependencies.Dependency):
2126            raise InterpreterException('Second argument must be a dependency object')
2127        identifier = dependencies.get_dep_identifier(name, kwargs)
2128        for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
2129        override = self.build.dependency_overrides[for_machine].get(identifier)
2130        if override:
2131            m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}'
2132            location = mlog.get_error_location_string(override.node.filename, override.node.lineno)
2133            raise InterpreterException(m.format(name, location))
2134        self.build.dependency_overrides[for_machine][identifier] = \
2135            build.DependencyOverride(dep, self.interpreter.current_node)
2136
2137    @noPosargs
2138    @permittedKwargs({})
2139    def project_version_method(self, args, kwargs):
2140        return self.build.dep_manifest[self.interpreter.active_projectname]['version']
2141
2142    @FeatureNew('meson.project_license()', '0.45.0')
2143    @noPosargs
2144    @permittedKwargs({})
2145    def project_license_method(self, args, kwargs):
2146        return self.build.dep_manifest[self.interpreter.active_projectname]['license']
2147
2148    @noPosargs
2149    @permittedKwargs({})
2150    def version_method(self, args, kwargs):
2151        return coredata.version
2152
2153    @noPosargs
2154    @permittedKwargs({})
2155    def project_name_method(self, args, kwargs):
2156        return self.interpreter.active_projectname
2157
2158    @noArgsFlattening
2159    @permittedKwargs({})
2160    def get_cross_property_method(self, args, kwargs) -> str:
2161        if len(args) < 1 or len(args) > 2:
2162            raise InterpreterException('Must have one or two arguments.')
2163        propname = args[0]
2164        if not isinstance(propname, str):
2165            raise InterpreterException('Property name must be string.')
2166        try:
2167            props = self.interpreter.environment.properties.host
2168            return props[propname]
2169        except Exception:
2170            if len(args) == 2:
2171                return args[1]
2172            raise InterpreterException('Unknown cross property: %s.' % propname)
2173
2174    @noArgsFlattening
2175    @permittedKwargs({'native'})
2176    @FeatureNew('meson.get_external_property', '0.54.0')
2177    def get_external_property_method(self, args: T.Sequence[str], kwargs: dict) -> str:
2178        if len(args) < 1 or len(args) > 2:
2179            raise InterpreterException('Must have one or two positional arguments.')
2180        propname = args[0]
2181        if not isinstance(propname, str):
2182            raise InterpreterException('Property name must be string.')
2183
2184        def _get_native() -> str:
2185            try:
2186                props = self.interpreter.environment.properties.build
2187                return props[propname]
2188            except Exception:
2189                if len(args) == 2:
2190                    return args[1]
2191                raise InterpreterException('Unknown native property: %s.' % propname)
2192        if 'native' in kwargs:
2193            if kwargs['native']:
2194                return _get_native()
2195            else:
2196                return self.get_cross_property_method(args, {})
2197        else:  # native: not specified
2198            if self.build.environment.is_cross_build():
2199                return self.get_cross_property_method(args, kwargs)
2200            else:
2201                return _get_native()
2202
2203known_library_kwargs = (
2204    build.known_shlib_kwargs |
2205    build.known_stlib_kwargs
2206)
2207
2208known_build_target_kwargs = (
2209    known_library_kwargs |
2210    build.known_exe_kwargs |
2211    build.known_jar_kwargs |
2212    {'target_type'}
2213)
2214
2215_base_test_args = {'args', 'depends', 'env', 'should_fail', 'timeout', 'workdir', 'suite', 'priority', 'protocol'}
2216
2217permitted_kwargs = {'add_global_arguments': {'language', 'native'},
2218                    'add_global_link_arguments': {'language', 'native'},
2219                    'add_languages': {'required', 'native'},
2220                    'add_project_link_arguments': {'language', 'native'},
2221                    'add_project_arguments': {'language', 'native'},
2222                    'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default'},
2223                    'benchmark': _base_test_args,
2224                    'build_target': known_build_target_kwargs,
2225                    'configure_file': {'input',
2226                                       'output',
2227                                       'configuration',
2228                                       'command',
2229                                       'copy',
2230                                       'depfile',
2231                                       'install_dir',
2232                                       'install_mode',
2233                                       'capture',
2234                                       'install',
2235                                       'format',
2236                                       'output_format',
2237                                       'encoding'},
2238                    'custom_target': {'input',
2239                                      'output',
2240                                      'command',
2241                                      'install',
2242                                      'install_dir',
2243                                      'install_mode',
2244                                      'build_always',
2245                                      'capture',
2246                                      'depends',
2247                                      'depend_files',
2248                                      'depfile',
2249                                      'build_by_default',
2250                                      'build_always_stale',
2251                                      'console'},
2252                    'dependency': {'default_options',
2253                                   'embed',
2254                                   'fallback',
2255                                   'language',
2256                                   'main',
2257                                   'method',
2258                                   'modules',
2259                                   'components',
2260                                   'cmake_module_path',
2261                                   'optional_modules',
2262                                   'native',
2263                                   'not_found_message',
2264                                   'required',
2265                                   'static',
2266                                   'version',
2267                                   'private_headers',
2268                                   'cmake_args',
2269                                   'include_type',
2270                                   },
2271                    'declare_dependency': {'include_directories',
2272                                           'link_with',
2273                                           'sources',
2274                                           'dependencies',
2275                                           'compile_args',
2276                                           'link_args',
2277                                           'link_whole',
2278                                           'version',
2279                                           'variables',
2280                                           },
2281                    'executable': build.known_exe_kwargs,
2282                    'find_program': {'required', 'native', 'version', 'dirs'},
2283                    'generator': {'arguments',
2284                                  'output',
2285                                  'depends',
2286                                  'depfile',
2287                                  'capture',
2288                                  'preserve_path_from'},
2289                    'include_directories': {'is_system'},
2290                    'install_data': {'install_dir', 'install_mode', 'rename', 'sources'},
2291                    'install_headers': {'install_dir', 'install_mode', 'subdir'},
2292                    'install_man': {'install_dir', 'install_mode'},
2293                    'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'},
2294                    'jar': build.known_jar_kwargs,
2295                    'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
2296                    'run_command': {'check', 'capture', 'env'},
2297                    'run_target': {'command', 'depends'},
2298                    'shared_library': build.known_shlib_kwargs,
2299                    'shared_module': build.known_shmod_kwargs,
2300                    'static_library': build.known_stlib_kwargs,
2301                    'both_libraries': known_library_kwargs,
2302                    'library': known_library_kwargs,
2303                    'subdir': {'if_found'},
2304                    'subproject': {'version', 'default_options', 'required'},
2305                    'test': set.union(_base_test_args, {'is_parallel'}),
2306                    'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'},
2307                    }
2308
2309
2310class Interpreter(InterpreterBase):
2311
2312    def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects',
2313                 modules = None, default_project_options=None, mock=False, ast=None):
2314        super().__init__(build.environment.get_source_dir(), subdir, subproject)
2315        self.an_unpicklable_object = mesonlib.an_unpicklable_object
2316        self.build = build
2317        self.environment = build.environment
2318        self.coredata = self.environment.get_coredata()
2319        self.backend = backend
2320        self.summary = {}
2321        if modules is None:
2322            self.modules = {}
2323        else:
2324            self.modules = modules
2325        # Subproject directory is usually the name of the subproject, but can
2326        # be different for dependencies provided by wrap files.
2327        self.subproject_directory_name = subdir.split(os.path.sep)[-1]
2328        self.subproject_dir = subproject_dir
2329        self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
2330        if not mock and ast is None:
2331            self.load_root_meson_file()
2332            self.sanity_check_ast()
2333        elif ast is not None:
2334            self.ast = ast
2335            self.sanity_check_ast()
2336        self.builtin.update({'meson': MesonMain(build, self)})
2337        self.generators = []
2338        self.visited_subdirs = {}
2339        self.project_args_frozen = False
2340        self.global_args_frozen = False  # implies self.project_args_frozen
2341        self.subprojects = {}
2342        self.subproject_stack = []
2343        self.configure_file_outputs = {}
2344        # Passed from the outside, only used in subprojects.
2345        if default_project_options:
2346            self.default_project_options = default_project_options.copy()
2347        else:
2348            self.default_project_options = {}
2349        self.project_default_options = {}
2350        self.build_func_dict()
2351        # build_def_files needs to be defined before parse_project is called
2352        self.build_def_files = [os.path.join(self.subdir, environment.build_filename)]
2353        if not mock:
2354            self.parse_project()
2355        self._redetect_machines()
2356
2357    def _redetect_machines(self):
2358        # Re-initialize machine descriptions. We can do a better job now because we
2359        # have the compilers needed to gain more knowledge, so wipe out old
2360        # inference and start over.
2361        machines = self.build.environment.machines.miss_defaulting()
2362        machines.build = environment.detect_machine_info(self.coredata.compilers.build)
2363        self.build.environment.machines = machines.default_missing()
2364        assert self.build.environment.machines.build.cpu is not None
2365        assert self.build.environment.machines.host.cpu is not None
2366        assert self.build.environment.machines.target.cpu is not None
2367
2368        self.builtin['build_machine'] = \
2369            MachineHolder(self.build.environment.machines.build)
2370        self.builtin['host_machine'] = \
2371            MachineHolder(self.build.environment.machines.host)
2372        self.builtin['target_machine'] = \
2373            MachineHolder(self.build.environment.machines.target)
2374
2375    def get_non_matching_default_options(self):
2376        env = self.environment
2377        for def_opt_name, def_opt_value in self.project_default_options.items():
2378            for opts in env.coredata.get_all_options():
2379                cur_opt_value = opts.get(def_opt_name)
2380                if cur_opt_value is not None:
2381                    def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value)
2382                    if def_opt_value != cur_opt_value.value:
2383                        yield (def_opt_name, def_opt_value, cur_opt_value)
2384
2385    def build_func_dict(self):
2386        self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
2387                           'add_project_arguments': self.func_add_project_arguments,
2388                           'add_global_link_arguments': self.func_add_global_link_arguments,
2389                           'add_project_link_arguments': self.func_add_project_link_arguments,
2390                           'add_test_setup': self.func_add_test_setup,
2391                           'add_languages': self.func_add_languages,
2392                           'alias_target': self.func_alias_target,
2393                           'assert': self.func_assert,
2394                           'benchmark': self.func_benchmark,
2395                           'build_target': self.func_build_target,
2396                           'configuration_data': self.func_configuration_data,
2397                           'configure_file': self.func_configure_file,
2398                           'custom_target': self.func_custom_target,
2399                           'declare_dependency': self.func_declare_dependency,
2400                           'dependency': self.func_dependency,
2401                           'disabler': self.func_disabler,
2402                           'environment': self.func_environment,
2403                           'error': self.func_error,
2404                           'executable': self.func_executable,
2405                           'generator': self.func_generator,
2406                           'gettext': self.func_gettext,
2407                           'get_option': self.func_get_option,
2408                           'get_variable': self.func_get_variable,
2409                           'files': self.func_files,
2410                           'find_library': self.func_find_library,
2411                           'find_program': self.func_find_program,
2412                           'include_directories': self.func_include_directories,
2413                           'import': self.func_import,
2414                           'install_data': self.func_install_data,
2415                           'install_headers': self.func_install_headers,
2416                           'install_man': self.func_install_man,
2417                           'install_subdir': self.func_install_subdir,
2418                           'is_disabler': self.func_is_disabler,
2419                           'is_variable': self.func_is_variable,
2420                           'jar': self.func_jar,
2421                           'join_paths': self.func_join_paths,
2422                           'library': self.func_library,
2423                           'message': self.func_message,
2424                           'warning': self.func_warning,
2425                           'option': self.func_option,
2426                           'project': self.func_project,
2427                           'run_target': self.func_run_target,
2428                           'run_command': self.func_run_command,
2429                           'set_variable': self.func_set_variable,
2430                           'subdir': self.func_subdir,
2431                           'subdir_done': self.func_subdir_done,
2432                           'subproject': self.func_subproject,
2433                           'summary': self.func_summary,
2434                           'shared_library': self.func_shared_lib,
2435                           'shared_module': self.func_shared_module,
2436                           'static_library': self.func_static_lib,
2437                           'both_libraries': self.func_both_lib,
2438                           'test': self.func_test,
2439                           'vcs_tag': self.func_vcs_tag
2440                           })
2441        if 'MESON_UNIT_TEST' in os.environ:
2442            self.funcs.update({'exception': self.func_exception})
2443
2444    def holderify(self, item):
2445        if isinstance(item, list):
2446            return [self.holderify(x) for x in item]
2447        if isinstance(item, dict):
2448            return {k: self.holderify(v) for k, v in item.items()}
2449
2450        if isinstance(item, build.CustomTarget):
2451            return CustomTargetHolder(item, self)
2452        elif isinstance(item, (int, str, bool, Disabler, InterpreterObject)) or item is None:
2453            return item
2454        elif isinstance(item, build.Executable):
2455            return ExecutableHolder(item, self)
2456        elif isinstance(item, build.GeneratedList):
2457            return GeneratedListHolder(item)
2458        elif isinstance(item, build.RunTarget):
2459            raise RuntimeError('This is not a pipe.')
2460        elif isinstance(item, build.RunScript):
2461            raise RuntimeError('Do not do this.')
2462        elif isinstance(item, build.Data):
2463            return DataHolder(item)
2464        elif isinstance(item, dependencies.Dependency):
2465            return DependencyHolder(item, self.subproject)
2466        elif isinstance(item, dependencies.ExternalProgram):
2467            return ExternalProgramHolder(item, self.subproject)
2468        elif hasattr(item, 'held_object'):
2469            return item
2470        else:
2471            raise InterpreterException('Module returned a value of unknown type.')
2472
2473    def process_new_values(self, invalues):
2474        invalues = listify(invalues)
2475        for v in invalues:
2476            if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)):
2477                v = v.held_object
2478
2479            if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)):
2480                self.add_target(v.name, v)
2481            elif isinstance(v, list):
2482                self.module_method_callback(v)
2483            elif isinstance(v, build.GeneratedList):
2484                pass
2485            elif isinstance(v, build.RunScript):
2486                self.build.install_scripts.append(v)
2487            elif isinstance(v, build.Data):
2488                self.build.data.append(v)
2489            elif isinstance(v, dependencies.ExternalProgram):
2490                return ExternalProgramHolder(v, self.subproject)
2491            elif isinstance(v, dependencies.InternalDependency):
2492                # FIXME: This is special cased and not ideal:
2493                # The first source is our new VapiTarget, the rest are deps
2494                self.process_new_values(v.sources[0])
2495            elif hasattr(v, 'held_object'):
2496                pass
2497            elif isinstance(v, (int, str, bool, Disabler)):
2498                pass
2499            else:
2500                raise InterpreterException('Module returned a value of unknown type.')
2501
2502    def module_method_callback(self, return_object):
2503        if not isinstance(return_object, ModuleReturnValue):
2504            raise InterpreterException('Bug in module, it returned an invalid object')
2505        invalues = return_object.new_objects
2506        self.process_new_values(invalues)
2507        return self.holderify(return_object.return_value)
2508
2509    def get_build_def_files(self):
2510        return self.build_def_files
2511
2512    def add_build_def_file(self, f):
2513        # Use relative path for files within source directory, and absolute path
2514        # for system files. Skip files within build directory. Also skip not regular
2515        # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this
2516        # is especially important to convert '/' to '\' on Windows.
2517        if isinstance(f, mesonlib.File):
2518            if f.is_built:
2519                return
2520            f = os.path.normpath(f.relative_name())
2521        elif os.path.isfile(f) and not f.startswith('/dev'):
2522            srcdir = Path(self.environment.get_source_dir())
2523            builddir = Path(self.environment.get_build_dir())
2524            try:
2525                f = Path(f).resolve()
2526            except OSError:
2527                f = Path(f)
2528                s = f.stat()
2529                if (hasattr(s, 'st_file_attributes') and
2530                        s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and
2531                        s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK):
2532                    # This is a Windows Store link which we can't
2533                    # resolve, so just do our best otherwise.
2534                    f = f.parent.resolve() / f.name
2535                else:
2536                    raise
2537            if builddir in f.parents:
2538                return
2539            if srcdir in f.parents:
2540                f = f.relative_to(srcdir)
2541            f = str(f)
2542        else:
2543            return
2544        if f not in self.build_def_files:
2545            self.build_def_files.append(f)
2546
2547    def get_variables(self):
2548        return self.variables
2549
2550    def check_stdlibs(self):
2551        for for_machine in MachineChoice:
2552            props = self.build.environment.properties[for_machine]
2553            for l in self.coredata.compilers[for_machine].keys():
2554                try:
2555                    di = mesonlib.stringlistify(props.get_stdlib(l))
2556                    if len(di) != 2:
2557                        raise InterpreterException('Stdlib definition for %s should have exactly two elements.'
2558                                                   % l)
2559                    projname, depname = di
2560                    subproj = self.do_subproject(projname, 'meson', {})
2561                    self.build.stdlibs.host[l] = subproj.get_variable_method([depname], {})
2562                except KeyError:
2563                    pass
2564                except InvalidArguments:
2565                    pass
2566
2567    @stringArgs
2568    @noKwargs
2569    def func_import(self, node, args, kwargs):
2570        if len(args) != 1:
2571            raise InvalidCode('Import takes one argument.')
2572        modname = args[0]
2573        if modname.startswith('unstable-'):
2574            plainname = modname.split('-', 1)[1]
2575            mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node)
2576            modname = 'unstable_' + plainname
2577        if modname not in self.modules:
2578            try:
2579                module = importlib.import_module('mesonbuild.modules.' + modname)
2580            except ImportError:
2581                raise InvalidArguments('Module "%s" does not exist' % (modname, ))
2582            self.modules[modname] = module.initialize(self)
2583        return ModuleHolder(modname, self.modules[modname], self)
2584
2585    @stringArgs
2586    @noKwargs
2587    def func_files(self, node, args, kwargs):
2588        return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args]
2589
2590    @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole'])
2591    @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables'])
2592    @permittedKwargs(permitted_kwargs['declare_dependency'])
2593    @noPosargs
2594    def func_declare_dependency(self, node, args, kwargs):
2595        version = kwargs.get('version', self.project_version)
2596        if not isinstance(version, str):
2597            raise InterpreterException('Version must be a string.')
2598        incs = self.extract_incdirs(kwargs)
2599        libs = unholder(extract_as_list(kwargs, 'link_with'))
2600        libs_whole = unholder(extract_as_list(kwargs, 'link_whole'))
2601        sources = extract_as_list(kwargs, 'sources')
2602        sources = unholder(listify(self.source_strings_to_files(sources)))
2603        deps = unholder(extract_as_list(kwargs, 'dependencies'))
2604        compile_args = mesonlib.stringlistify(kwargs.get('compile_args', []))
2605        link_args = mesonlib.stringlistify(kwargs.get('link_args', []))
2606        variables = kwargs.get('variables', {})
2607        if not isinstance(variables, dict):
2608            raise InterpreterException('variables must be a dict.')
2609        if not all(isinstance(v, str) for v in variables.values()):
2610            # Because that is how they will come from pkg-config and cmake
2611            raise InterpreterException('variables values be strings.')
2612        final_deps = []
2613        for d in deps:
2614            try:
2615                d = d.held_object
2616            except Exception:
2617                pass
2618            if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)):
2619                raise InterpreterException('Dependencies must be external deps')
2620            final_deps.append(d)
2621        for l in libs:
2622            if isinstance(l, dependencies.Dependency):
2623                raise InterpreterException('''Entries in "link_with" may only be self-built targets,
2624external dependencies (including libraries) must go to "dependencies".''')
2625        dep = dependencies.InternalDependency(version, incs, compile_args,
2626                                              link_args, libs, libs_whole, sources, final_deps,
2627                                              variables)
2628        return DependencyHolder(dep, self.subproject)
2629
2630    @noKwargs
2631    def func_assert(self, node, args, kwargs):
2632        if len(args) == 1:
2633            FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject)
2634            value = args[0]
2635            message = None
2636        elif len(args) == 2:
2637            value, message = args
2638            if not isinstance(message, str):
2639                raise InterpreterException('Assert message not a string.')
2640        else:
2641            raise InterpreterException('Assert takes between one and two arguments')
2642        if not isinstance(value, bool):
2643            raise InterpreterException('Assert value not bool.')
2644        if not value:
2645            if message is None:
2646                from .ast import AstPrinter
2647                printer = AstPrinter()
2648                node.args.arguments[0].accept(printer)
2649                message = printer.result
2650            raise InterpreterException('Assert failed: ' + message)
2651
2652    def validate_arguments(self, args, argcount, arg_types):
2653        if argcount is not None:
2654            if argcount != len(args):
2655                raise InvalidArguments('Expected %d arguments, got %d.' %
2656                                       (argcount, len(args)))
2657        for actual, wanted in zip(args, arg_types):
2658            if wanted is not None:
2659                if not isinstance(actual, wanted):
2660                    raise InvalidArguments('Incorrect argument type.')
2661
2662    @FeatureNewKwargs('run_command', '0.50.0', ['env'])
2663    @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture'])
2664    @permittedKwargs(permitted_kwargs['run_command'])
2665    def func_run_command(self, node, args, kwargs):
2666        return self.run_command_impl(node, args, kwargs)
2667
2668    def run_command_impl(self, node, args, kwargs, in_builddir=False):
2669        if len(args) < 1:
2670            raise InterpreterException('Not enough arguments')
2671        cmd, *cargs = args
2672        capture = kwargs.get('capture', True)
2673        srcdir = self.environment.get_source_dir()
2674        builddir = self.environment.get_build_dir()
2675
2676        check = kwargs.get('check', False)
2677        if not isinstance(check, bool):
2678            raise InterpreterException('Check must be boolean.')
2679
2680        env = self.unpack_env_kwarg(kwargs)
2681
2682        m = 'must be a string, or the output of find_program(), files() '\
2683            'or configure_file(), or a compiler object; not {!r}'
2684        expanded_args = []
2685        if isinstance(cmd, ExternalProgramHolder):
2686            cmd = cmd.held_object
2687            if isinstance(cmd, build.Executable):
2688                progname = node.args.arguments[0].value
2689                msg = 'Program {!r} was overridden with the compiled executable {!r}'\
2690                      ' and therefore cannot be used during configuration'
2691                raise InterpreterException(msg.format(progname, cmd.description()))
2692            if not cmd.found():
2693                raise InterpreterException('command {!r} not found or not executable'.format(cmd.get_name()))
2694        elif isinstance(cmd, CompilerHolder):
2695            exelist = cmd.compiler.get_exelist()
2696            cmd = exelist[0]
2697            prog = ExternalProgram(cmd, silent=True)
2698            if not prog.found():
2699                raise InterpreterException('Program {!r} not found '
2700                                           'or not executable'.format(cmd))
2701            cmd = prog
2702            expanded_args = exelist[1:]
2703        else:
2704            if isinstance(cmd, mesonlib.File):
2705                cmd = cmd.absolute_path(srcdir, builddir)
2706            elif not isinstance(cmd, str):
2707                raise InterpreterException('First argument ' + m.format(cmd))
2708            # Prefer scripts in the current source directory
2709            search_dir = os.path.join(srcdir, self.subdir)
2710            prog = ExternalProgram(cmd, silent=True, search_dir=search_dir)
2711            if not prog.found():
2712                raise InterpreterException('Program or command {!r} not found '
2713                                           'or not executable'.format(cmd))
2714            cmd = prog
2715        for a in listify(cargs):
2716            if isinstance(a, str):
2717                expanded_args.append(a)
2718            elif isinstance(a, mesonlib.File):
2719                expanded_args.append(a.absolute_path(srcdir, builddir))
2720            elif isinstance(a, ExternalProgramHolder):
2721                expanded_args.append(a.held_object.get_path())
2722            else:
2723                raise InterpreterException('Arguments ' + m.format(a))
2724        # If any file that was used as an argument to the command
2725        # changes, we must re-run the configuration step.
2726        self.add_build_def_file(cmd.get_path())
2727        for a in expanded_args:
2728            if not os.path.isabs(a):
2729                a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a)
2730            self.add_build_def_file(a)
2731        return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir,
2732                          self.environment.get_build_command() + ['introspect'],
2733                          in_builddir=in_builddir, check=check, capture=capture)
2734
2735    @stringArgs
2736    def func_gettext(self, nodes, args, kwargs):
2737        raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead')
2738
2739    def func_option(self, nodes, args, kwargs):
2740        raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.')
2741
2742    @FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
2743    @permittedKwargs(permitted_kwargs['subproject'])
2744    @stringArgs
2745    def func_subproject(self, nodes, args, kwargs):
2746        if len(args) != 1:
2747            raise InterpreterException('Subproject takes exactly one argument')
2748        dirname = args[0]
2749        return self.do_subproject(dirname, 'meson', kwargs)
2750
2751    def disabled_subproject(self, dirname, disabled_feature=None, exception=None):
2752        sub = SubprojectHolder(None, self.subproject_dir, dirname,
2753                               disabled_feature=disabled_feature, exception=exception)
2754        self.subprojects[dirname] = sub
2755        return sub
2756
2757    def get_subproject(self, dirname):
2758        sub = self.subprojects.get(dirname)
2759        if sub and sub.found():
2760            return sub
2761        return None
2762
2763    def do_subproject(self, dirname: str, method: str, kwargs):
2764        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
2765        if disabled:
2766            mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
2767            return self.disabled_subproject(dirname, disabled_feature=feature)
2768
2769        default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
2770        default_options = coredata.create_options_dict(default_options)
2771        if dirname == '':
2772            raise InterpreterException('Subproject dir name must not be empty.')
2773        if dirname[0] == '.':
2774            raise InterpreterException('Subproject dir name must not start with a period.')
2775        if '..' in dirname:
2776            raise InterpreterException('Subproject name must not contain a ".." path segment.')
2777        if os.path.isabs(dirname):
2778            raise InterpreterException('Subproject name must not be an absolute path.')
2779        if has_path_sep(dirname):
2780            mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.',
2781                         location=self.current_node)
2782        if dirname in self.subproject_stack:
2783            fullstack = self.subproject_stack + [dirname]
2784            incpath = ' => '.join(fullstack)
2785            raise InvalidCode('Recursive include of subprojects: %s.' % incpath)
2786        if dirname in self.subprojects:
2787            subproject = self.subprojects[dirname]
2788            if required and not subproject.found():
2789                raise InterpreterException('Subproject "%s/%s" required but not found.' % (
2790                                           self.subproject_dir, dirname))
2791            return subproject
2792
2793        r = self.environment.wrap_resolver
2794        try:
2795            resolved = r.resolve(dirname, method, self.subproject)
2796        except wrap.WrapException as e:
2797            subprojdir = os.path.join(self.subproject_dir, r.directory)
2798            if isinstance(e, wrap.WrapNotFoundException):
2799                # if the reason subproject execution failed was because
2800                # the directory doesn't exist, try to give some helpful
2801                # advice if it's a nested subproject that needs
2802                # promotion...
2803                self.print_nested_info(dirname)
2804            if not required:
2805                mlog.log(e)
2806                mlog.log('Subproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)')
2807                return self.disabled_subproject(dirname, exception=e)
2808            raise e
2809
2810        subdir = os.path.join(self.subproject_dir, resolved)
2811        subdir_abs = os.path.join(self.environment.get_source_dir(), subdir)
2812        os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
2813        self.global_args_frozen = True
2814
2815        mlog.log()
2816        with mlog.nested():
2817            mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n')
2818        try:
2819            if method == 'meson':
2820                return self._do_subproject_meson(dirname, subdir, default_options, kwargs)
2821            elif method == 'cmake':
2822                return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, kwargs)
2823            else:
2824                raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname))
2825        # Invalid code is always an error
2826        except InvalidCode:
2827            raise
2828        except Exception as e:
2829            if not required:
2830                with mlog.nested():
2831                    # Suppress the 'ERROR:' prefix because this exception is not
2832                    # fatal and VS CI treat any logs with "ERROR:" as fatal.
2833                    mlog.exception(e, prefix=mlog.yellow('Exception:'))
2834                mlog.log('\nSubproject', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)')
2835                return self.disabled_subproject(dirname, exception=e)
2836            raise e
2837
2838    def _do_subproject_meson(self, dirname, subdir, default_options, kwargs, ast=None, build_def_files=None):
2839        with mlog.nested():
2840            new_build = self.build.copy()
2841            subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
2842                               self.modules, default_options, ast=ast)
2843            subi.subprojects = self.subprojects
2844
2845            subi.subproject_stack = self.subproject_stack + [dirname]
2846            current_active = self.active_projectname
2847            current_warnings_counter = mlog.log_warnings_counter
2848            mlog.log_warnings_counter = 0
2849            subi.run()
2850            subi_warnings = mlog.log_warnings_counter
2851            mlog.log_warnings_counter = current_warnings_counter
2852
2853            mlog.log('Subproject', mlog.bold(dirname), 'finished.')
2854
2855        mlog.log()
2856
2857        if 'version' in kwargs:
2858            pv = subi.project_version
2859            wanted = kwargs['version']
2860            if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
2861                raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted))
2862        self.active_projectname = current_active
2863        self.subprojects.update(subi.subprojects)
2864        self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname,
2865                                                     warnings=subi_warnings)
2866        # Duplicates are possible when subproject uses files from project root
2867        if build_def_files:
2868            self.build_def_files = list(set(self.build_def_files + build_def_files))
2869        else:
2870            self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
2871        self.build.merge(subi.build)
2872        self.build.subprojects[dirname] = subi.project_version
2873        self.summary.update(subi.summary)
2874        return self.subprojects[dirname]
2875
2876    def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwargs):
2877        with mlog.nested():
2878            new_build = self.build.copy()
2879            prefix = self.coredata.builtins['prefix'].value
2880
2881            from .modules.cmake import CMakeSubprojectOptions
2882            options = kwargs.get('options', CMakeSubprojectOptions())
2883            if not isinstance(options, CMakeSubprojectOptions):
2884                raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions'
2885                                           ' object (created by cmake.subproject_options())')
2886
2887            cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
2888            cmake_options += options.cmake_options
2889            cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend)
2890            cm_int.initialise(cmake_options)
2891            cm_int.analyse()
2892
2893            # Generate a meson ast and execute it with the normal do_subproject_meson
2894            ast = cm_int.pretend_to_be_meson(options.target_options)
2895
2896            mlog.log()
2897            with mlog.nested():
2898                mlog.log('Processing generated meson AST')
2899
2900                # Debug print the generated meson file
2901                from .ast import AstIndentationGenerator, AstPrinter
2902                printer = AstPrinter()
2903                ast.accept(AstIndentationGenerator())
2904                ast.accept(printer)
2905                printer.post_process()
2906                meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build')
2907                with open(meson_filename, "w") as f:
2908                    f.write(printer.result)
2909
2910                mlog.log('Build file:', meson_filename)
2911                mlog.cmd_ci_include(meson_filename)
2912                mlog.log()
2913
2914            result = self._do_subproject_meson(dirname, subdir, default_options, kwargs, ast, cm_int.bs_files)
2915            result.cm_interpreter = cm_int
2916
2917        mlog.log()
2918        return result
2919
2920    def get_option_internal(self, optname):
2921        raw_optname = optname
2922        if self.is_subproject():
2923            optname = self.subproject + ':' + optname
2924
2925        for opts in [
2926                self.coredata.base_options, compilers.base_options, self.coredata.builtins,
2927                dict(self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine)),
2928                dict(self.coredata.flatten_lang_iterator(
2929                    self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options))),
2930        ]:
2931            v = opts.get(optname)
2932            if v is None or v.yielding:
2933                v = opts.get(raw_optname)
2934            if v is not None:
2935                return v
2936
2937        try:
2938            opt = self.coredata.user_options[optname]
2939            if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options:
2940                popt = self.coredata.user_options[raw_optname]
2941                if type(opt) is type(popt):
2942                    opt = popt
2943                else:
2944                    # Get class name, then option type as a string
2945                    opt_type = opt.__class__.__name__[4:][:-6].lower()
2946                    popt_type = popt.__class__.__name__[4:][:-6].lower()
2947                    # This is not a hard error to avoid dependency hell, the workaround
2948                    # when this happens is to simply set the subproject's option directly.
2949                    mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield '
2950                                 'to parent option of type {3!r}, ignoring parent value. '
2951                                 'Use -D{2}:{0}=value to set the value for this option manually'
2952                                 '.'.format(raw_optname, opt_type, self.subproject, popt_type),
2953                                 location=self.current_node)
2954            return opt
2955        except KeyError:
2956            pass
2957
2958        raise InterpreterException('Tried to access unknown option "%s".' % optname)
2959
2960    @stringArgs
2961    @noKwargs
2962    def func_get_option(self, nodes, args, kwargs):
2963        if len(args) != 1:
2964            raise InterpreterException('Argument required for get_option.')
2965        optname = args[0]
2966        if ':' in optname:
2967            raise InterpreterException('Having a colon in option name is forbidden, '
2968                                       'projects are not allowed to directly access '
2969                                       'options of other subprojects.')
2970        opt = self.get_option_internal(optname)
2971        if isinstance(opt, coredata.UserFeatureOption):
2972            return FeatureOptionHolder(self.environment, optname, opt)
2973        elif isinstance(opt, coredata.UserOption):
2974            return opt.value
2975        return opt
2976
2977    @noKwargs
2978    def func_configuration_data(self, node, args, kwargs):
2979        if len(args) > 1:
2980            raise InterpreterException('configuration_data takes only one optional positional arguments')
2981        elif len(args) == 1:
2982            FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject)
2983            initial_values = args[0]
2984            if not isinstance(initial_values, dict):
2985                raise InterpreterException('configuration_data first argument must be a dictionary')
2986        else:
2987            initial_values = {}
2988        return ConfigurationDataHolder(self.subproject, initial_values)
2989
2990    def set_backend(self):
2991        # The backend is already set when parsing subprojects
2992        if self.backend is not None:
2993            return
2994        backend = self.coredata.get_builtin_option('backend')
2995        from .backend import backends
2996        self.backend = backends.get_backend_from_name(backend, self.build, self)
2997
2998        if self.backend is None:
2999            raise InterpreterException('Unknown backend "%s".' % backend)
3000        if backend != self.backend.name:
3001            if self.backend.name.startswith('vs'):
3002                mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
3003            self.coredata.set_builtin_option('backend', self.backend.name)
3004
3005        # Only init backend options on first invocation otherwise it would
3006        # override values previously set from command line.
3007        if self.environment.first_invocation:
3008            self.coredata.init_backend_options(backend)
3009
3010        options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')}
3011        self.coredata.set_options(options)
3012
3013    @stringArgs
3014    @permittedKwargs(permitted_kwargs['project'])
3015    def func_project(self, node, args, kwargs):
3016        if len(args) < 1:
3017            raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
3018        proj_name, *proj_langs = args
3019        if ':' in proj_name:
3020            raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name))
3021
3022        # This needs to be evaluated as early as possible, as meson uses this
3023        # for things like deprecation testing.
3024        if 'meson_version' in kwargs:
3025            cv = coredata.version
3026            pv = kwargs['meson_version']
3027            if not mesonlib.version_compare(cv, pv):
3028                raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv))
3029            mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
3030
3031        if os.path.exists(self.option_file):
3032            oi = optinterpreter.OptionInterpreter(self.subproject)
3033            oi.process(self.option_file)
3034            self.coredata.merge_user_options(oi.options)
3035            self.add_build_def_file(self.option_file)
3036
3037        # Do not set default_options on reconfigure otherwise it would override
3038        # values previously set from command line. That means that changing
3039        # default_options in a project will trigger a reconfigure but won't
3040        # have any effect.
3041        self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
3042        self.project_default_options = coredata.create_options_dict(self.project_default_options)
3043        if self.environment.first_invocation:
3044            default_options = self.project_default_options
3045            default_options.update(self.default_project_options)
3046            self.coredata.init_builtins(self.subproject)
3047        else:
3048            default_options = {}
3049        self.coredata.set_default_options(default_options, self.subproject, self.environment)
3050
3051        if not self.is_subproject():
3052            self.build.project_name = proj_name
3053        self.active_projectname = proj_name
3054        self.project_version = kwargs.get('version', 'undefined')
3055        if self.build.project_version is None:
3056            self.build.project_version = self.project_version
3057        proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown'))
3058        self.build.dep_manifest[proj_name] = {'version': self.project_version,
3059                                              'license': proj_license}
3060        if self.subproject in self.build.projects:
3061            raise InvalidCode('Second call to project().')
3062        if not self.is_subproject() and 'subproject_dir' in kwargs:
3063            spdirname = kwargs['subproject_dir']
3064            if not isinstance(spdirname, str):
3065                raise InterpreterException('Subproject_dir must be a string')
3066            if os.path.isabs(spdirname):
3067                raise InterpreterException('Subproject_dir must not be an absolute path.')
3068            if spdirname.startswith('.'):
3069                raise InterpreterException('Subproject_dir must not begin with a period.')
3070            if '..' in spdirname:
3071                raise InterpreterException('Subproject_dir must not contain a ".." segment.')
3072            self.subproject_dir = spdirname
3073
3074        self.build.subproject_dir = self.subproject_dir
3075        if not self.is_subproject():
3076            wrap_mode = self.coredata.get_builtin_option('wrap_mode')
3077            subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
3078            self.environment.wrap_resolver = wrap.Resolver(subproject_dir_abs, wrap_mode)
3079
3080        self.build.projects[self.subproject] = proj_name
3081        mlog.log('Project name:', mlog.bold(proj_name))
3082        mlog.log('Project version:', mlog.bold(self.project_version))
3083        self.add_languages(proj_langs, True, MachineChoice.BUILD)
3084        self.add_languages(proj_langs, True, MachineChoice.HOST)
3085        self.set_backend()
3086        if not self.is_subproject():
3087            self.check_stdlibs()
3088
3089    @FeatureNewKwargs('add_languages', '0.54.0', ['native'])
3090    @permittedKwargs(permitted_kwargs['add_languages'])
3091    @stringArgs
3092    def func_add_languages(self, node, args, kwargs):
3093        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
3094        if disabled:
3095            for lang in sorted(args, key=compilers.sort_clink):
3096                mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled')
3097            return False
3098        if 'native' in kwargs:
3099            return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs))
3100        else:
3101            # absent 'native' means 'both' for backwards compatibility
3102            tv = FeatureNew.get_target_version(self.subproject)
3103            if FeatureNew.check_version(tv, '0.54.0'):
3104                mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.',
3105                             location=self.current_node)
3106
3107            success = self.add_languages(args, False, MachineChoice.BUILD)
3108            success &= self.add_languages(args, required, MachineChoice.HOST)
3109            return success
3110
3111    def get_message_string_arg(self, arg):
3112        if isinstance(arg, list):
3113            argstr = stringifyUserArguments(arg)
3114        elif isinstance(arg, dict):
3115            argstr = stringifyUserArguments(arg)
3116        elif isinstance(arg, str):
3117            argstr = arg
3118        elif isinstance(arg, int):
3119            argstr = str(arg)
3120        else:
3121            raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
3122
3123        return argstr
3124
3125    @noArgsFlattening
3126    @noKwargs
3127    def func_message(self, node, args, kwargs):
3128        if len(args) > 1:
3129            FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject)
3130        args_str = [self.get_message_string_arg(i) for i in args]
3131        self.message_impl(args_str)
3132
3133    def message_impl(self, args):
3134        mlog.log(mlog.bold('Message:'), *args)
3135
3136    @noArgsFlattening
3137    @FeatureNewKwargs('summary', '0.54.0', ['list_sep'])
3138    @permittedKwargs({'section', 'bool_yn', 'list_sep'})
3139    @FeatureNew('summary', '0.53.0')
3140    def func_summary(self, node, args, kwargs):
3141        if len(args) == 1:
3142            if not isinstance(args[0], dict):
3143                raise InterpreterException('Summary first argument must be dictionary.')
3144            values = args[0]
3145        elif len(args) == 2:
3146            if not isinstance(args[0], str):
3147                raise InterpreterException('Summary first argument must be string.')
3148            values = {args[0]: args[1]}
3149        else:
3150            raise InterpreterException('Summary accepts at most 2 arguments.')
3151        section = kwargs.get('section', '')
3152        if not isinstance(section, str):
3153            raise InterpreterException('Summary\'s section keyword argument must be string.')
3154        self.summary_impl(section, values, kwargs)
3155
3156    def summary_impl(self, section, values, kwargs):
3157        if self.subproject not in self.summary:
3158            self.summary[self.subproject] = Summary(self.active_projectname, self.project_version)
3159        self.summary[self.subproject].add_section(section, values, kwargs)
3160
3161    def _print_summary(self):
3162        # Add automatic 'Supbrojects' section in main project.
3163        all_subprojects = collections.OrderedDict()
3164        for name, subp in sorted(self.subprojects.items()):
3165            value = subp.found()
3166            if subp.disabled_feature:
3167                value = [value, 'Feature {!r} disabled'.format(subp.disabled_feature)]
3168            elif subp.exception:
3169                value = [value, str(subp.exception)]
3170            elif subp.warnings > 0:
3171                value = [value, '{} warnings'.format(subp.warnings)]
3172            all_subprojects[name] = value
3173        if all_subprojects:
3174            self.summary_impl('Subprojects', all_subprojects,
3175                              {'bool_yn': True,
3176                               'list_sep': ' ',
3177                              })
3178        # Print all summaries, main project last.
3179        mlog.log('')  # newline
3180        main_summary = self.summary.pop('', None)
3181        for _, summary in sorted(self.summary.items()):
3182            summary.dump()
3183        if main_summary:
3184            main_summary.dump()
3185
3186    @noArgsFlattening
3187    @FeatureNew('warning', '0.44.0')
3188    @noKwargs
3189    def func_warning(self, node, args, kwargs):
3190        if len(args) > 1:
3191            FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject)
3192        args_str = [self.get_message_string_arg(i) for i in args]
3193        mlog.warning(*args_str, location=node)
3194
3195    @noKwargs
3196    def func_error(self, node, args, kwargs):
3197        self.validate_arguments(args, 1, [str])
3198        raise InterpreterException('Problem encountered: ' + args[0])
3199
3200    @noKwargs
3201    def func_exception(self, node, args, kwargs):
3202        self.validate_arguments(args, 0, [])
3203        raise Exception()
3204
3205    def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool:
3206        success = self.add_languages_for(args, required, for_machine)
3207        if not self.coredata.is_cross_build():
3208            self.coredata.copy_build_options_from_regular_ones()
3209        self._redetect_machines()
3210        return success
3211
3212    def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool:
3213        should = self.environment.properties.host.get('skip_sanity_check', False)
3214        if not isinstance(should, bool):
3215            raise InterpreterException('Option skip_sanity_check must be a boolean.')
3216        if for_machine != MachineChoice.HOST and not should:
3217            return False
3218        if not self.environment.is_cross_build() and not should:
3219            return False
3220        return should
3221
3222    def add_languages_for(self, args, required, for_machine: MachineChoice):
3223        args = [a.lower() for a in args]
3224        langs = set(self.coredata.compilers[for_machine].keys())
3225        langs.update(args)
3226        if 'vala' in langs:
3227            if 'c' not in langs:
3228                raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.')
3229
3230        success = True
3231        for lang in sorted(args, key=compilers.sort_clink):
3232            clist = self.coredata.compilers[for_machine]
3233            machine_name = for_machine.get_lower_case_name()
3234            if lang in clist:
3235                comp = clist[lang]
3236            else:
3237                try:
3238                    comp = self.environment.detect_compiler_for(lang, for_machine)
3239                    if comp is None:
3240                        raise InvalidArguments('Tried to use unknown language "%s".' % lang)
3241                    if self.should_skip_sanity_check(for_machine):
3242                        mlog.log_once('Cross compiler sanity tests disabled via the cross file.')
3243                    else:
3244                        comp.sanity_check(self.environment.get_scratch_dir(), self.environment)
3245                except Exception:
3246                    if not required:
3247                        mlog.log('Compiler for language',
3248                                 mlog.bold(lang), 'for the', machine_name,
3249                                 'machine not found.')
3250                        success = False
3251                        continue
3252                    else:
3253                        raise
3254
3255            if for_machine == MachineChoice.HOST or self.environment.is_cross_build():
3256                logger_fun = mlog.log
3257            else:
3258                logger_fun = mlog.debug
3259            logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:',
3260                       mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string())
3261            if comp.linker is not None:
3262                logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:',
3263                           mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version)
3264            self.build.ensure_static_linker(comp)
3265
3266        return success
3267
3268    def program_from_file_for(self, for_machine, prognames):
3269        for p in unholder(prognames):
3270            if isinstance(p, mesonlib.File):
3271                continue # Always points to a local (i.e. self generated) file.
3272            if not isinstance(p, str):
3273                raise InterpreterException('Executable name must be a string')
3274            prog = ExternalProgram.from_bin_list(self.environment, for_machine, p)
3275            if prog.found():
3276                return ExternalProgramHolder(prog, self.subproject)
3277        return None
3278
3279    def program_from_system(self, args, search_dirs, silent=False):
3280        # Search for scripts relative to current subdir.
3281        # Do not cache found programs because find_program('foobar')
3282        # might give different results when run from different source dirs.
3283        source_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
3284        for exename in args:
3285            if isinstance(exename, mesonlib.File):
3286                if exename.is_built:
3287                    search_dir = os.path.join(self.environment.get_build_dir(),
3288                                              exename.subdir)
3289                else:
3290                    search_dir = os.path.join(self.environment.get_source_dir(),
3291                                              exename.subdir)
3292                exename = exename.fname
3293                extra_search_dirs = []
3294            elif isinstance(exename, str):
3295                search_dir = source_dir
3296                extra_search_dirs = search_dirs
3297            else:
3298                raise InvalidArguments('find_program only accepts strings and '
3299                                       'files, not {!r}'.format(exename))
3300            extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
3301                                                   extra_search_dirs=extra_search_dirs,
3302                                                   silent=silent)
3303            progobj = ExternalProgramHolder(extprog, self.subproject)
3304            if progobj.found():
3305                return progobj
3306
3307    def program_from_overrides(self, command_names, extra_info):
3308        for name in command_names:
3309            if not isinstance(name, str):
3310                continue
3311            if name in self.build.find_overrides:
3312                exe = self.build.find_overrides[name]
3313                extra_info.append(mlog.blue('(overriden)'))
3314                return ExternalProgramHolder(exe, self.subproject, self.backend)
3315        return None
3316
3317    def store_name_lookups(self, command_names):
3318        for name in command_names:
3319            if isinstance(name, str):
3320                self.build.searched_programs.add(name)
3321
3322    def add_find_program_override(self, name, exe):
3323        if name in self.build.searched_programs:
3324            raise InterpreterException('Tried to override finding of executable "%s" which has already been found.'
3325                                       % name)
3326        if name in self.build.find_overrides:
3327            raise InterpreterException('Tried to override executable "%s" which has already been overridden.'
3328                                       % name)
3329        self.build.find_overrides[name] = exe
3330
3331    def notfound_program(self, args):
3332        return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
3333
3334    # TODO update modules to always pass `for_machine`. It is bad-form to assume
3335    # the host machine.
3336    def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST,
3337                          required=True, silent=True, wanted='', search_dirs=None,
3338                          version_func=None):
3339        args = mesonlib.listify(args)
3340
3341        extra_info = []
3342        progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info)
3343        if progobj is None:
3344            progobj = self.notfound_program(args)
3345
3346        if not progobj.found():
3347            mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'))
3348            if required:
3349                m = 'Program {!r} not found'
3350                raise InterpreterException(m.format(progobj.get_name()))
3351            return progobj
3352
3353        if wanted:
3354            if version_func:
3355                version = version_func(progobj)
3356            else:
3357                version = progobj.get_version(self)
3358            is_found, not_found, found = mesonlib.version_compare_many(version, wanted)
3359            if not is_found:
3360                mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'),
3361                         'found', mlog.normal_cyan(version), 'but need:',
3362                         mlog.bold(', '.join(["'{}'".format(e) for e in not_found])))
3363                if required:
3364                    m = 'Invalid version of program, need {!r} {!r} found {!r}.'
3365                    raise InterpreterException(m.format(progobj.get_name(), not_found, version))
3366                return self.notfound_program(args)
3367            extra_info.insert(0, mlog.normal_cyan(version))
3368
3369        # Only store successful lookups
3370        self.store_name_lookups(args)
3371        mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.green('YES'), *extra_info)
3372        return progobj
3373
3374    def program_lookup(self, args, for_machine, required, search_dirs, extra_info):
3375        progobj = self.program_from_overrides(args, extra_info)
3376        if progobj:
3377            return progobj
3378
3379        fallback = None
3380        wrap_mode = self.coredata.get_builtin_option('wrap_mode')
3381        if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver:
3382            fallback = self.environment.wrap_resolver.find_program_provider(args)
3383        if fallback and wrap_mode == WrapMode.forcefallback:
3384            return self.find_program_fallback(fallback, args, required, extra_info)
3385
3386        progobj = self.program_from_file_for(for_machine, args)
3387        if progobj is None:
3388            progobj = self.program_from_system(args, search_dirs, silent=True)
3389        if progobj is None and args[0].endswith('python3'):
3390            prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True)
3391            progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None
3392        if progobj is None and fallback and required:
3393            progobj = self.find_program_fallback(fallback, args, required, extra_info)
3394
3395        return progobj
3396
3397    def find_program_fallback(self, fallback, args, required, extra_info):
3398        mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program',
3399                 mlog.bold(' '.join(args)))
3400        sp_kwargs = { 'required': required }
3401        self.do_subproject(fallback, 'meson', sp_kwargs)
3402        return self.program_from_overrides(args, extra_info)
3403
3404    @FeatureNewKwargs('find_program', '0.53.0', ['dirs'])
3405    @FeatureNewKwargs('find_program', '0.52.0', ['version'])
3406    @FeatureNewKwargs('find_program', '0.49.0', ['disabler'])
3407    @disablerIfNotFound
3408    @permittedKwargs(permitted_kwargs['find_program'])
3409    def func_find_program(self, node, args, kwargs):
3410        if not args:
3411            raise InterpreterException('No program name specified.')
3412
3413        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
3414        if disabled:
3415            mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled')
3416            return self.notfound_program(args)
3417
3418        search_dirs = extract_search_dirs(kwargs)
3419        wanted = mesonlib.stringlistify(kwargs.get('version', []))
3420        for_machine = self.machine_from_native_kwarg(kwargs)
3421        return self.find_program_impl(args, for_machine, required=required,
3422                                      silent=False, wanted=wanted,
3423                                      search_dirs=search_dirs)
3424
3425    def func_find_library(self, node, args, kwargs):
3426        raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
3427                          'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n'
3428                          'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
3429                          )
3430
3431    def _find_cached_dep(self, name, display_name, kwargs):
3432        # Check if we want this as a build-time / build machine or runt-time /
3433        # host machine dep.
3434        for_machine = self.machine_from_native_kwarg(kwargs)
3435        identifier = dependencies.get_dep_identifier(name, kwargs)
3436        wanted_vers = mesonlib.stringlistify(kwargs.get('version', []))
3437
3438        override = self.build.dependency_overrides[for_machine].get(identifier)
3439        if override:
3440            info = [mlog.blue('(overridden)' if override.explicit else '(cached)')]
3441            cached_dep = override.dep
3442            # We don't implicitly override not-found dependencies, but user could
3443            # have explicitly called meson.override_dependency() with a not-found
3444            # dep.
3445            if not cached_dep.found():
3446                mlog.log('Dependency', mlog.bold(display_name),
3447                         'found:', mlog.red('NO'), *info)
3448                return identifier, cached_dep
3449            found_vers = cached_dep.get_version()
3450            if not self.check_version(wanted_vers, found_vers):
3451                mlog.log('Dependency', mlog.bold(name),
3452                         'found:', mlog.red('NO'),
3453                         'found', mlog.normal_cyan(found_vers), 'but need:',
3454                         mlog.bold(', '.join(["'{}'".format(e) for e in wanted_vers])),
3455                         *info)
3456                return identifier, NotFoundDependency(self.environment)
3457        else:
3458            info = [mlog.blue('(cached)')]
3459            cached_dep = self.coredata.deps[for_machine].get(identifier)
3460            if cached_dep:
3461                found_vers = cached_dep.get_version()
3462                if not self.check_version(wanted_vers, found_vers):
3463                    return identifier, None
3464
3465        if cached_dep:
3466            if found_vers:
3467                info = [mlog.normal_cyan(found_vers), *info]
3468            mlog.log('Dependency', mlog.bold(display_name),
3469                     'found:', mlog.green('YES'), *info)
3470            return identifier, cached_dep
3471
3472        return identifier, None
3473
3474    @staticmethod
3475    def check_version(wanted, found):
3476        if not wanted:
3477            return True
3478        if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]:
3479            return False
3480        return True
3481
3482    def notfound_dependency(self):
3483        return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
3484
3485    def verify_fallback_consistency(self, dirname, varname, cached_dep):
3486        subi = self.get_subproject(dirname)
3487        if not cached_dep or not varname or not subi or not cached_dep.found():
3488            return
3489        dep = subi.get_variable_method([varname], {})
3490        if dep.held_object != cached_dep:
3491            m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}'
3492            raise DependencyException(m.format(varname))
3493
3494    def get_subproject_dep(self, name, display_name, dirname, varname, kwargs):
3495        required = kwargs.get('required', True)
3496        wanted = mesonlib.stringlistify(kwargs.get('version', []))
3497        subproj_path = os.path.join(self.subproject_dir, dirname)
3498        dep = self.notfound_dependency()
3499        try:
3500            subproject = self.subprojects[dirname]
3501            _, cached_dep = self._find_cached_dep(name, display_name, kwargs)
3502            if varname is None:
3503                # Assuming the subproject overridden the dependency we want
3504                if cached_dep:
3505                    if required and not cached_dep.found():
3506                        m = 'Dependency {!r} is not satisfied'
3507                        raise DependencyException(m.format(display_name))
3508                    return DependencyHolder(cached_dep, self.subproject)
3509                else:
3510                    if required:
3511                        m = 'Subproject {} did not override dependency {}'
3512                        raise DependencyException(m.format(subproj_path, display_name))
3513                    mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
3514                             mlog.bold(subproj_path), 'found:', mlog.red('NO'))
3515                    return self.notfound_dependency()
3516            if subproject.found():
3517                self.verify_fallback_consistency(dirname, varname, cached_dep)
3518                dep = self.subprojects[dirname].get_variable_method([varname], {})
3519        except InvalidArguments:
3520            pass
3521
3522        if not isinstance(dep, DependencyHolder):
3523            raise InvalidCode('Fetched variable {!r} in the subproject {!r} is '
3524                              'not a dependency object.'.format(varname, dirname))
3525
3526        if not dep.found():
3527            if required:
3528                raise DependencyException('Could not find dependency {} in subproject {}'
3529                                          ''.format(varname, dirname))
3530            # If the dependency is not required, don't raise an exception
3531            mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
3532                     mlog.bold(subproj_path), 'found:', mlog.red('NO'))
3533            return dep
3534
3535        found = dep.held_object.get_version()
3536        if not self.check_version(wanted, found):
3537            if required:
3538                raise DependencyException('Version {} of subproject dependency {} already '
3539                                          'cached, requested incompatible version {} for '
3540                                          'dep {}'.format(found, dirname, wanted, display_name))
3541
3542            mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
3543                     mlog.bold(subproj_path), 'found:', mlog.red('NO'),
3544                     'found', mlog.normal_cyan(found), 'but need:',
3545                     mlog.bold(', '.join(["'{}'".format(e) for e in wanted])))
3546            return self.notfound_dependency()
3547
3548        found = mlog.normal_cyan(found) if found else None
3549        mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
3550                 mlog.bold(subproj_path), 'found:', mlog.green('YES'), found)
3551        return dep
3552
3553    def _handle_featurenew_dependencies(self, name):
3554        'Do a feature check on dependencies used by this subproject'
3555        if name == 'mpi':
3556            FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
3557        elif name == 'pcap':
3558            FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
3559        elif name == 'vulkan':
3560            FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
3561        elif name == 'libwmf':
3562            FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
3563        elif name == 'openmp':
3564            FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
3565
3566    @FeatureNewKwargs('dependency', '0.54.0', ['components'])
3567    @FeatureNewKwargs('dependency', '0.52.0', ['include_type'])
3568    @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args'])
3569    @FeatureNewKwargs('dependency', '0.49.0', ['disabler'])
3570    @FeatureNewKwargs('dependency', '0.40.0', ['method'])
3571    @FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
3572    @disablerIfNotFound
3573    @permittedKwargs(permitted_kwargs['dependency'])
3574    def func_dependency(self, node, args, kwargs):
3575        self.validate_arguments(args, 1, [str])
3576        name = args[0]
3577        display_name = name if name else '(anonymous)'
3578        mods = extract_as_list(kwargs, 'modules')
3579        if mods:
3580            display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
3581        not_found_message = kwargs.get('not_found_message', '')
3582        if not isinstance(not_found_message, str):
3583            raise InvalidArguments('The not_found_message must be a string.')
3584        try:
3585            d = self.dependency_impl(name, display_name, kwargs)
3586        except Exception:
3587            if not_found_message:
3588                self.message_impl([not_found_message])
3589            raise
3590        if not d.found() and not_found_message:
3591            self.message_impl([not_found_message])
3592            self.message_impl([not_found_message])
3593        # Override this dependency to have consistent results in subsequent
3594        # dependency lookups.
3595        if name and d.found():
3596            for_machine = self.machine_from_native_kwarg(kwargs)
3597            identifier = dependencies.get_dep_identifier(name, kwargs)
3598            if identifier not in self.build.dependency_overrides[for_machine]:
3599                self.build.dependency_overrides[for_machine][identifier] = \
3600                    build.DependencyOverride(d.held_object, node, explicit=False)
3601        return d
3602
3603    def dependency_impl(self, name, display_name, kwargs):
3604        disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
3605        if disabled:
3606            mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
3607            return self.notfound_dependency()
3608
3609        has_fallback = 'fallback' in kwargs
3610        if not has_fallback and name:
3611            # Add an implicit fallback if we have a wrap file or a directory with the same name,
3612            # but only if this dependency is required. It is common to first check for a pkg-config,
3613            # then fallback to use find_library() and only afterward check again the dependency
3614            # with a fallback. If the fallback has already been configured then we have to use it
3615            # even if the dependency is not required.
3616            provider = self.environment.wrap_resolver.find_dep_provider(name)
3617            dirname = mesonlib.listify(provider)[0]
3618            if provider and (required or self.get_subproject(dirname)):
3619                kwargs['fallback'] = provider
3620                has_fallback = True
3621
3622        if 'default_options' in kwargs and not has_fallback:
3623            mlog.warning('The "default_options" keyworg argument does nothing without a "fallback" keyword argument.',
3624                         location=self.current_node)
3625
3626        # writing just "dependency('')" is an error, because it can only fail
3627        if name == '' and required and not has_fallback:
3628            raise InvalidArguments('Dependency is both required and not-found')
3629
3630        if '<' in name or '>' in name or '=' in name:
3631            raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
3632                                   'version\n requirements use the \'version\' keyword argument instead.')
3633
3634        identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs)
3635        if cached_dep:
3636            if has_fallback:
3637                dirname, varname = self.get_subproject_infos(kwargs)
3638                self.verify_fallback_consistency(dirname, varname, cached_dep)
3639            if required and not cached_dep.found():
3640                m = 'Dependency {!r} was already checked and was not found'
3641                raise DependencyException(m.format(display_name))
3642            return DependencyHolder(cached_dep, self.subproject)
3643
3644        # If the dependency has already been configured, possibly by
3645        # a higher level project, try to use it first.
3646        if has_fallback:
3647            dirname, varname = self.get_subproject_infos(kwargs)
3648            if self.get_subproject(dirname):
3649                return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
3650
3651        wrap_mode = self.coredata.get_builtin_option('wrap_mode')
3652        force_fallback_for = self.coredata.get_builtin_option('force_fallback_for')
3653        forcefallback = has_fallback and (wrap_mode == WrapMode.forcefallback or \
3654                                          name in force_fallback_for or \
3655                                          dirname in force_fallback_for)
3656        if name != '' and not forcefallback:
3657            self._handle_featurenew_dependencies(name)
3658            kwargs['required'] = required and not has_fallback
3659            dep = dependencies.find_external_dependency(name, self.environment, kwargs)
3660            kwargs['required'] = required
3661            # Only store found-deps in the cache
3662            # Never add fallback deps to self.coredata.deps since we
3663            # cannot cache them. They must always be evaluated else
3664            # we won't actually read all the build files.
3665            if dep.found():
3666                for_machine = self.machine_from_native_kwarg(kwargs)
3667                self.coredata.deps[for_machine].put(identifier, dep)
3668                return DependencyHolder(dep, self.subproject)
3669
3670        if has_fallback:
3671            return self.dependency_fallback(name, display_name, kwargs)
3672
3673        return self.notfound_dependency()
3674
3675    @FeatureNew('disabler', '0.44.0')
3676    @noKwargs
3677    @noPosargs
3678    def func_disabler(self, node, args, kwargs):
3679        return Disabler()
3680
3681    def print_nested_info(self, dependency_name):
3682        message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' +
3683                   'To use it in the current project, promote it by going in the project source\n'
3684                   'root and issuing']
3685        sprojs = mesonlib.detect_subprojects('subprojects', self.source_root)
3686        if dependency_name not in sprojs:
3687            return
3688        found = sprojs[dependency_name]
3689        if len(found) > 1:
3690            message.append('one of the following commands:')
3691        else:
3692            message.append('the following command:')
3693        command_templ = '\nmeson wrap promote {}'
3694        for l in found:
3695            message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:])))
3696        mlog.warning(*message, location=self.current_node)
3697
3698    def get_subproject_infos(self, kwargs):
3699        fbinfo = mesonlib.stringlistify(kwargs['fallback'])
3700        if len(fbinfo) == 1:
3701            FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject)
3702            return fbinfo[0], None
3703        elif len(fbinfo) != 2:
3704            raise InterpreterException('Fallback info must have one or two items.')
3705        return fbinfo
3706
3707    def dependency_fallback(self, name, display_name, kwargs):
3708        dirname, varname = self.get_subproject_infos(kwargs)
3709        required = kwargs.get('required', True)
3710
3711        # Explicitly listed fallback preferences for specific subprojects
3712        # take precedence over wrap-mode
3713        force_fallback_for = self.coredata.get_builtin_option('force_fallback_for')
3714        if name in force_fallback_for or dirname in force_fallback_for:
3715            mlog.log('Looking for a fallback subproject for the dependency',
3716                     mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject')
3717        elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback:
3718            mlog.log('Not looking for a fallback subproject for the dependency',
3719                     mlog.bold(display_name), 'because:\nUse of fallback '
3720                     'dependencies is disabled.')
3721            if required:
3722                m = 'Dependency {!r} not found and fallback is disabled'
3723                raise DependencyException(m.format(display_name))
3724            return self.notfound_dependency()
3725        elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.forcefallback:
3726            mlog.log('Looking for a fallback subproject for the dependency',
3727                     mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.')
3728        else:
3729            mlog.log('Looking for a fallback subproject for the dependency',
3730                     mlog.bold(display_name))
3731        sp_kwargs = {
3732            'default_options': kwargs.get('default_options', []),
3733            'required': required,
3734        }
3735        self.do_subproject(dirname, 'meson', sp_kwargs)
3736        return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
3737
3738    @FeatureNewKwargs('executable', '0.42.0', ['implib'])
3739    @permittedKwargs(permitted_kwargs['executable'])
3740    def func_executable(self, node, args, kwargs):
3741        return self.build_target(node, args, kwargs, ExecutableHolder)
3742
3743    @permittedKwargs(permitted_kwargs['static_library'])
3744    def func_static_lib(self, node, args, kwargs):
3745        return self.build_target(node, args, kwargs, StaticLibraryHolder)
3746
3747    @permittedKwargs(permitted_kwargs['shared_library'])
3748    def func_shared_lib(self, node, args, kwargs):
3749        holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
3750        holder.held_object.shared_library_only = True
3751        return holder
3752
3753    @permittedKwargs(permitted_kwargs['both_libraries'])
3754    def func_both_lib(self, node, args, kwargs):
3755        return self.build_both_libraries(node, args, kwargs)
3756
3757    @FeatureNew('shared_module', '0.37.0')
3758    @permittedKwargs(permitted_kwargs['shared_module'])
3759    def func_shared_module(self, node, args, kwargs):
3760        return self.build_target(node, args, kwargs, SharedModuleHolder)
3761
3762    @permittedKwargs(permitted_kwargs['library'])
3763    def func_library(self, node, args, kwargs):
3764        return self.build_library(node, args, kwargs)
3765
3766    @permittedKwargs(permitted_kwargs['jar'])
3767    def func_jar(self, node, args, kwargs):
3768        return self.build_target(node, args, kwargs, JarHolder)
3769
3770    @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
3771    @permittedKwargs(permitted_kwargs['build_target'])
3772    def func_build_target(self, node, args, kwargs):
3773        if 'target_type' not in kwargs:
3774            raise InterpreterException('Missing target_type keyword argument')
3775        target_type = kwargs.pop('target_type')
3776        if target_type == 'executable':
3777            return self.build_target(node, args, kwargs, ExecutableHolder)
3778        elif target_type == 'shared_library':
3779            return self.build_target(node, args, kwargs, SharedLibraryHolder)
3780        elif target_type == 'shared_module':
3781            FeatureNew('build_target(target_type: \'shared_module\')',
3782                       '0.51.0').use(self.subproject)
3783            return self.build_target(node, args, kwargs, SharedModuleHolder)
3784        elif target_type == 'static_library':
3785            return self.build_target(node, args, kwargs, StaticLibraryHolder)
3786        elif target_type == 'both_libraries':
3787            return self.build_both_libraries(node, args, kwargs)
3788        elif target_type == 'library':
3789            return self.build_library(node, args, kwargs)
3790        elif target_type == 'jar':
3791            return self.build_target(node, args, kwargs, JarHolder)
3792        else:
3793            raise InterpreterException('Unknown target_type.')
3794
3795    @permittedKwargs(permitted_kwargs['vcs_tag'])
3796    @FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'],
3797                             'combine build_by_default and build_always_stale instead.')
3798    def func_vcs_tag(self, node, args, kwargs):
3799        if 'input' not in kwargs or 'output' not in kwargs:
3800            raise InterpreterException('Keyword arguments input and output must exist')
3801        if 'fallback' not in kwargs:
3802            FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject)
3803        fallback = kwargs.pop('fallback', self.project_version)
3804        if not isinstance(fallback, str):
3805            raise InterpreterException('Keyword argument fallback must be a string.')
3806        replace_string = kwargs.pop('replace_string', '@VCS_TAG@')
3807        regex_selector = '(.*)' # default regex selector for custom command: use complete output
3808        vcs_cmd = kwargs.get('command', None)
3809        if vcs_cmd and not isinstance(vcs_cmd, list):
3810            vcs_cmd = [vcs_cmd]
3811        source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir))
3812        if vcs_cmd:
3813            # Is the command an executable in path or maybe a script in the source tree?
3814            vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0])
3815        else:
3816            vcs = mesonlib.detect_vcs(source_dir)
3817            if vcs:
3818                mlog.log('Found %s repository at %s' % (vcs['name'], vcs['wc_dir']))
3819                vcs_cmd = vcs['get_rev'].split()
3820                regex_selector = vcs['rev_regex']
3821            else:
3822                vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string
3823        # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command...
3824        kwargs['command'] = self.environment.get_build_command() + \
3825            ['--internal',
3826             'vcstagger',
3827             '@INPUT0@',
3828             '@OUTPUT0@',
3829             fallback,
3830             source_dir,
3831             replace_string,
3832             regex_selector] + vcs_cmd
3833        kwargs.setdefault('build_by_default', True)
3834        kwargs.setdefault('build_always_stale', True)
3835        return self._func_custom_target_impl(node, [kwargs['output']], kwargs)
3836
3837    @FeatureNew('subdir_done', '0.46.0')
3838    @stringArgs
3839    def func_subdir_done(self, node, args, kwargs):
3840        if len(kwargs) > 0:
3841            raise InterpreterException('exit does not take named arguments')
3842        if len(args) > 0:
3843            raise InterpreterException('exit does not take any arguments')
3844        raise SubdirDoneRequest()
3845
3846    @stringArgs
3847    @FeatureNewKwargs('custom_target', '0.48.0', ['console'])
3848    @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale'])
3849    @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default'])
3850    @permittedKwargs(permitted_kwargs['custom_target'])
3851    def func_custom_target(self, node, args, kwargs):
3852        if len(args) != 1:
3853            raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
3854        if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
3855            FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject)
3856        return self._func_custom_target_impl(node, args, kwargs)
3857
3858    def _func_custom_target_impl(self, node, args, kwargs):
3859        'Implementation-only, without FeatureNew checks, for internal use'
3860        name = args[0]
3861        kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
3862        if 'input' in kwargs:
3863            try:
3864                kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
3865            except mesonlib.MesonException:
3866                mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s).
3867This will become a hard error in the future.''' % kwargs['input'], location=self.current_node)
3868        tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self)
3869        self.add_target(name, tg.held_object)
3870        return tg
3871
3872    @permittedKwargs(permitted_kwargs['run_target'])
3873    def func_run_target(self, node, args, kwargs):
3874        if len(args) > 1:
3875            raise InvalidCode('Run_target takes only one positional argument: the target name.')
3876        elif len(args) == 1:
3877            if 'command' not in kwargs:
3878                raise InterpreterException('Missing "command" keyword argument')
3879            all_args = extract_as_list(kwargs, 'command')
3880            deps = unholder(extract_as_list(kwargs, 'depends'))
3881        else:
3882            raise InterpreterException('Run_target needs at least one positional argument.')
3883
3884        cleaned_args = []
3885        for i in unholder(listify(all_args)):
3886            if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)):
3887                mlog.debug('Wrong type:', str(i))
3888                raise InterpreterException('Invalid argument to run_target.')
3889            if isinstance(i, dependencies.ExternalProgram) and not i.found():
3890                raise InterpreterException('Tried to use non-existing executable {!r}'.format(i.name))
3891            cleaned_args.append(i)
3892        name = args[0]
3893        if not isinstance(name, str):
3894            raise InterpreterException('First argument must be a string.')
3895        cleaned_deps = []
3896        for d in deps:
3897            if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
3898                raise InterpreterException('Depends items must be build targets.')
3899            cleaned_deps.append(d)
3900        command, *cmd_args = cleaned_args
3901        tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self)
3902        self.add_target(name, tg.held_object)
3903        full_name = (self.subproject, name)
3904        assert(full_name not in self.build.run_target_names)
3905        self.build.run_target_names.add(full_name)
3906        return tg
3907
3908    @FeatureNew('alias_target', '0.52.0')
3909    @noKwargs
3910    def func_alias_target(self, node, args, kwargs):
3911        if len(args) < 2:
3912            raise InvalidCode('alias_target takes at least 2 arguments.')
3913        name = args[0]
3914        if not isinstance(name, str):
3915            raise InterpreterException('First argument must be a string.')
3916        deps = unholder(listify(args[1:]))
3917        for d in deps:
3918            if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
3919                raise InterpreterException('Depends items must be build targets.')
3920        tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self)
3921        self.add_target(name, tg.held_object)
3922        return tg
3923
3924    @permittedKwargs(permitted_kwargs['generator'])
3925    def func_generator(self, node, args, kwargs):
3926        gen = GeneratorHolder(self, args, kwargs)
3927        self.generators.append(gen)
3928        return gen
3929
3930    @FeatureNewKwargs('benchmark', '0.46.0', ['depends'])
3931    @FeatureNewKwargs('benchmark', '0.52.0', ['priority'])
3932    @permittedKwargs(permitted_kwargs['benchmark'])
3933    def func_benchmark(self, node, args, kwargs):
3934        # is_parallel isn't valid here, so make sure it isn't passed
3935        if 'is_parallel' in kwargs:
3936            del kwargs['is_parallel']
3937        self.add_test(node, args, kwargs, False)
3938
3939    @FeatureNewKwargs('test', '0.46.0', ['depends'])
3940    @FeatureNewKwargs('test', '0.52.0', ['priority'])
3941    @permittedKwargs(permitted_kwargs['test'])
3942    def func_test(self, node, args, kwargs):
3943        if kwargs.get('protocol') == 'gtest':
3944            FeatureNew.single_use('"gtest" protocol for tests', '0.55.0', self.subproject)
3945        self.add_test(node, args, kwargs, True)
3946
3947    def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables:
3948        envlist = kwargs.get('env', EnvironmentVariablesHolder())
3949        if isinstance(envlist, EnvironmentVariablesHolder):
3950            env = envlist.held_object
3951        elif isinstance(envlist, dict):
3952            FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject)
3953            env = EnvironmentVariablesHolder(envlist)
3954            env = env.held_object
3955        else:
3956            envlist = listify(envlist)
3957            # Convert from array to environment object
3958            env = EnvironmentVariablesHolder(envlist)
3959            env = env.held_object
3960        return env
3961
3962    def add_test(self, node, args, kwargs, is_base_test):
3963        if len(args) != 2:
3964            raise InterpreterException('test expects 2 arguments, {} given'.format(len(args)))
3965        if not isinstance(args[0], str):
3966            raise InterpreterException('First argument of test must be a string.')
3967        exe = args[1]
3968        if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)):
3969            if isinstance(exe, mesonlib.File):
3970                exe = self.func_find_program(node, args[1], {})
3971            else:
3972                raise InterpreterException('Second argument must be executable.')
3973        par = kwargs.get('is_parallel', True)
3974        if not isinstance(par, bool):
3975            raise InterpreterException('Keyword argument is_parallel must be a boolean.')
3976        cmd_args = unholder(extract_as_list(kwargs, 'args'))
3977        for i in cmd_args:
3978            if not isinstance(i, (str, mesonlib.File, build.Target)):
3979                raise InterpreterException('Command line arguments must be strings, files or targets.')
3980        env = self.unpack_env_kwarg(kwargs)
3981        should_fail = kwargs.get('should_fail', False)
3982        if not isinstance(should_fail, bool):
3983            raise InterpreterException('Keyword argument should_fail must be a boolean.')
3984        timeout = kwargs.get('timeout', 30)
3985        if 'workdir' in kwargs:
3986            workdir = kwargs['workdir']
3987            if not isinstance(workdir, str):
3988                raise InterpreterException('Workdir keyword argument must be a string.')
3989            if not os.path.isabs(workdir):
3990                raise InterpreterException('Workdir keyword argument must be an absolute path.')
3991        else:
3992            workdir = None
3993        if not isinstance(timeout, int):
3994            raise InterpreterException('Timeout must be an integer.')
3995        protocol = kwargs.get('protocol', 'exitcode')
3996        if protocol not in {'exitcode', 'tap', 'gtest'}:
3997            raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".')
3998        suite = []
3999        prj = self.subproject if self.is_subproject() else self.build.project_name
4000        for s in mesonlib.stringlistify(kwargs.get('suite', '')):
4001            if len(s) > 0:
4002                s = ':' + s
4003            suite.append(prj.replace(' ', '_').replace(':', '_') + s)
4004        depends = unholder(extract_as_list(kwargs, 'depends'))
4005        for dep in depends:
4006            if not isinstance(dep, (build.CustomTarget, build.BuildTarget)):
4007                raise InterpreterException('Depends items must be build targets.')
4008        priority = kwargs.get('priority', 0)
4009        if not isinstance(priority, int):
4010            raise InterpreterException('Keyword argument priority must be an integer.')
4011        t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args,
4012                 env, should_fail, timeout, workdir, protocol, priority)
4013        if is_base_test:
4014            self.build.tests.append(t)
4015            mlog.debug('Adding test', mlog.bold(args[0], True))
4016        else:
4017            self.build.benchmarks.append(t)
4018            mlog.debug('Adding benchmark', mlog.bold(args[0], True))
4019
4020    @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode'])
4021    @permittedKwargs(permitted_kwargs['install_headers'])
4022    def func_install_headers(self, node, args, kwargs):
4023        source_files = self.source_strings_to_files(args)
4024        kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
4025        h = Headers(source_files, kwargs)
4026        self.build.headers.append(h)
4027        return h
4028
4029    @FeatureNewKwargs('install_man', '0.47.0', ['install_mode'])
4030    @permittedKwargs(permitted_kwargs['install_man'])
4031    def func_install_man(self, node, args, kwargs):
4032        fargs = self.source_strings_to_files(args)
4033        kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
4034        m = Man(fargs, kwargs)
4035        self.build.man.append(m)
4036        return m
4037
4038    @FeatureNewKwargs('subdir', '0.44.0', ['if_found'])
4039    @permittedKwargs(permitted_kwargs['subdir'])
4040    def func_subdir(self, node, args, kwargs):
4041        self.validate_arguments(args, 1, [str])
4042        mesonlib.check_direntry_issues(args)
4043        if '..' in args[0]:
4044            raise InvalidArguments('Subdir contains ..')
4045        if self.subdir == '' and args[0] == self.subproject_dir:
4046            raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.')
4047        if self.subdir == '' and args[0].startswith('meson-'):
4048            raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().')
4049        for i in mesonlib.extract_as_list(kwargs, 'if_found'):
4050            if not hasattr(i, 'found_method'):
4051                raise InterpreterException('Object used in if_found does not have a found method.')
4052            if not i.found_method([], {}):
4053                return
4054        prev_subdir = self.subdir
4055        subdir = os.path.join(prev_subdir, args[0])
4056        if os.path.isabs(subdir):
4057            raise InvalidArguments('Subdir argument must be a relative path.')
4058        absdir = os.path.join(self.environment.get_source_dir(), subdir)
4059        symlinkless_dir = os.path.realpath(absdir)
4060        if symlinkless_dir in self.visited_subdirs:
4061            raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'
4062                                   % subdir)
4063        self.visited_subdirs[symlinkless_dir] = True
4064        self.subdir = subdir
4065        os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True)
4066        buildfilename = os.path.join(self.subdir, environment.build_filename)
4067        self.build_def_files.append(buildfilename)
4068        absname = os.path.join(self.environment.get_source_dir(), buildfilename)
4069        if not os.path.isfile(absname):
4070            self.subdir = prev_subdir
4071            raise InterpreterException("Non-existent build file '{!s}'".format(buildfilename))
4072        with open(absname, encoding='utf8') as f:
4073            code = f.read()
4074        assert(isinstance(code, str))
4075        try:
4076            codeblock = mparser.Parser(code, absname).parse()
4077        except mesonlib.MesonException as me:
4078            me.file = absname
4079            raise me
4080        try:
4081            self.evaluate_codeblock(codeblock)
4082        except SubdirDoneRequest:
4083            pass
4084        self.subdir = prev_subdir
4085
4086    def _get_kwarg_install_mode(self, kwargs):
4087        if kwargs.get('install_mode', None) is None:
4088            return None
4089        install_mode = []
4090        mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
4091        for m in mode:
4092            # We skip any arguments that are set to `false`
4093            if m is False:
4094                m = None
4095            install_mode.append(m)
4096        if len(install_mode) > 3:
4097            raise InvalidArguments('Keyword argument install_mode takes at '
4098                                   'most 3 arguments.')
4099        if len(install_mode) > 0 and install_mode[0] is not None and \
4100           not isinstance(install_mode[0], str):
4101            raise InvalidArguments('Keyword argument install_mode requires the '
4102                                   'permissions arg to be a string or false')
4103        return FileMode(*install_mode)
4104
4105    @FeatureNewKwargs('install_data', '0.46.0', ['rename'])
4106    @FeatureNewKwargs('install_data', '0.38.0', ['install_mode'])
4107    @permittedKwargs(permitted_kwargs['install_data'])
4108    def func_install_data(self, node, args, kwargs):
4109        kwsource = mesonlib.stringlistify(kwargs.get('sources', []))
4110        raw_sources = args + kwsource
4111        sources = []
4112        source_strings = []
4113        for s in raw_sources:
4114            if isinstance(s, mesonlib.File):
4115                sources.append(s)
4116            elif isinstance(s, str):
4117                source_strings.append(s)
4118            else:
4119                raise InvalidArguments('Argument must be string or file.')
4120        sources += self.source_strings_to_files(source_strings)
4121        install_dir = kwargs.get('install_dir', None)
4122        if not isinstance(install_dir, (str, type(None))):
4123            raise InvalidArguments('Keyword argument install_dir not a string.')
4124        install_mode = self._get_kwarg_install_mode(kwargs)
4125        rename = kwargs.get('rename', None)
4126        data = DataHolder(build.Data(sources, install_dir, install_mode, rename))
4127        self.build.data.append(data.held_object)
4128        return data
4129
4130    @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories'])
4131    @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode'])
4132    @permittedKwargs(permitted_kwargs['install_subdir'])
4133    @stringArgs
4134    def func_install_subdir(self, node, args, kwargs):
4135        if len(args) != 1:
4136            raise InvalidArguments('Install_subdir requires exactly one argument.')
4137        subdir = args[0]
4138        if 'install_dir' not in kwargs:
4139            raise InvalidArguments('Missing keyword argument install_dir')
4140        install_dir = kwargs['install_dir']
4141        if not isinstance(install_dir, str):
4142            raise InvalidArguments('Keyword argument install_dir not a string.')
4143        if 'strip_directory' in kwargs:
4144            if not isinstance(kwargs['strip_directory'], bool):
4145                raise InterpreterException('"strip_directory" keyword must be a boolean.')
4146            strip_directory = kwargs['strip_directory']
4147        else:
4148            strip_directory = False
4149        if 'exclude_files' in kwargs:
4150            exclude = extract_as_list(kwargs, 'exclude_files')
4151            for f in exclude:
4152                if not isinstance(f, str):
4153                    raise InvalidArguments('Exclude argument not a string.')
4154                elif os.path.isabs(f):
4155                    raise InvalidArguments('Exclude argument cannot be absolute.')
4156            exclude_files = set(exclude)
4157        else:
4158            exclude_files = set()
4159        if 'exclude_directories' in kwargs:
4160            exclude = extract_as_list(kwargs, 'exclude_directories')
4161            for d in exclude:
4162                if not isinstance(d, str):
4163                    raise InvalidArguments('Exclude argument not a string.')
4164                elif os.path.isabs(d):
4165                    raise InvalidArguments('Exclude argument cannot be absolute.')
4166            exclude_directories = set(exclude)
4167        else:
4168            exclude_directories = set()
4169        exclude = (exclude_files, exclude_directories)
4170        install_mode = self._get_kwarg_install_mode(kwargs)
4171        idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
4172        self.build.install_dirs.append(idir)
4173        return idir
4174
4175    @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding'])
4176    @FeatureNewKwargs('configure_file', '0.46.0', ['format'])
4177    @FeatureNewKwargs('configure_file', '0.41.0', ['capture'])
4178    @FeatureNewKwargs('configure_file', '0.50.0', ['install'])
4179    @FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
4180    @permittedKwargs(permitted_kwargs['configure_file'])
4181    def func_configure_file(self, node, args, kwargs):
4182        if len(args) > 0:
4183            raise InterpreterException("configure_file takes only keyword arguments.")
4184        if 'output' not in kwargs:
4185            raise InterpreterException('Required keyword argument "output" not defined.')
4186        actions = set(['configuration', 'command', 'copy']).intersection(kwargs.keys())
4187        if len(actions) == 0:
4188            raise InterpreterException('Must specify an action with one of these '
4189                                       'keyword arguments: \'configuration\', '
4190                                       '\'command\', or \'copy\'.')
4191        elif len(actions) == 2:
4192            raise InterpreterException('Must not specify both {!r} and {!r} '
4193                                       'keyword arguments since they are '
4194                                       'mutually exclusive.'.format(*actions))
4195        elif len(actions) == 3:
4196            raise InterpreterException('Must specify one of {!r}, {!r}, and '
4197                                       '{!r} keyword arguments since they are '
4198                                       'mutually exclusive.'.format(*actions))
4199        if 'capture' in kwargs:
4200            if not isinstance(kwargs['capture'], bool):
4201                raise InterpreterException('"capture" keyword must be a boolean.')
4202            if 'command' not in kwargs:
4203                raise InterpreterException('"capture" keyword requires "command" keyword.')
4204
4205        if 'format' in kwargs:
4206            fmt = kwargs['format']
4207            if not isinstance(fmt, str):
4208                raise InterpreterException('"format" keyword must be a string.')
4209        else:
4210            fmt = 'meson'
4211
4212        if fmt not in ('meson', 'cmake', 'cmake@'):
4213            raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".')
4214
4215        if 'output_format' in kwargs:
4216            output_format = kwargs['output_format']
4217            if not isinstance(output_format, str):
4218                raise InterpreterException('"output_format" keyword must be a string.')
4219        else:
4220            output_format = 'c'
4221
4222        if output_format not in ('c', 'nasm'):
4223            raise InterpreterException('"format" possible values are "c" or "nasm".')
4224
4225        if 'depfile' in kwargs:
4226            depfile = kwargs['depfile']
4227            if not isinstance(depfile, str):
4228                raise InterpreterException('depfile file name must be a string')
4229        else:
4230            depfile = None
4231
4232        # Validate input
4233        inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
4234        inputs_abs = []
4235        for f in inputs:
4236            if isinstance(f, mesonlib.File):
4237                inputs_abs.append(f.absolute_path(self.environment.source_dir,
4238                                                  self.environment.build_dir))
4239                self.add_build_def_file(f)
4240            else:
4241                raise InterpreterException('Inputs can only be strings or file objects')
4242        # Validate output
4243        output = kwargs['output']
4244        if not isinstance(output, str):
4245            raise InterpreterException('Output file name must be a string')
4246        if inputs_abs:
4247            values = mesonlib.get_filenames_templates_dict(inputs_abs, None)
4248            outputs = mesonlib.substitute_values([output], values)
4249            output = outputs[0]
4250            if depfile:
4251                depfile = mesonlib.substitute_values([depfile], values)[0]
4252        ofile_rpath = os.path.join(self.subdir, output)
4253        if ofile_rpath in self.configure_file_outputs:
4254            mesonbuildfile = os.path.join(self.subdir, 'meson.build')
4255            current_call = "{}:{}".format(mesonbuildfile, self.current_lineno)
4256            first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath])
4257            mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call)
4258        else:
4259            self.configure_file_outputs[ofile_rpath] = self.current_lineno
4260        if os.path.dirname(output) != '':
4261            raise InterpreterException('Output file name must not contain a subdirectory.')
4262        (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
4263        ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname)
4264        # Perform the appropriate action
4265        if 'configuration' in kwargs:
4266            conf = kwargs['configuration']
4267            if isinstance(conf, dict):
4268                FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject)
4269                conf = ConfigurationDataHolder(self.subproject, conf)
4270            elif not isinstance(conf, ConfigurationDataHolder):
4271                raise InterpreterException('Argument "configuration" is not of type configuration_data')
4272            mlog.log('Configuring', mlog.bold(output), 'using configuration')
4273            if len(inputs) > 1:
4274                raise InterpreterException('At most one input file can given in configuration mode')
4275            if inputs:
4276                os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
4277                file_encoding = kwargs.setdefault('encoding', 'utf-8')
4278                missing_variables, confdata_useless = \
4279                    mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object,
4280                                          fmt, file_encoding)
4281                if missing_variables:
4282                    var_list = ", ".join(map(repr, sorted(missing_variables)))
4283                    mlog.warning(
4284                        "The variable(s) %s in the input file '%s' are not "
4285                        "present in the given configuration data." % (
4286                            var_list, inputs[0]), location=node)
4287                if confdata_useless:
4288                    ifbase = os.path.basename(inputs_abs[0])
4289                    mlog.warning('Got an empty configuration_data() object and found no '
4290                                 'substitutions in the input file {!r}. If you want to '
4291                                 'copy a file to the build dir, use the \'copy:\' keyword '
4292                                 'argument added in 0.47.0'.format(ifbase), location=node)
4293            else:
4294                mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format)
4295            conf.mark_used()
4296        elif 'command' in kwargs:
4297            if len(inputs) > 1:
4298                FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject)
4299            # We use absolute paths for input and output here because the cwd
4300            # that the command is run from is 'unspecified', so it could change.
4301            # Currently it's builddir/subdir for in_builddir else srcdir/subdir.
4302            values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs])
4303            if depfile:
4304                depfile = os.path.join(self.environment.get_scratch_dir(), depfile)
4305                values['@DEPFILE@'] = depfile
4306            # Substitute @INPUT@, @OUTPUT@, etc here.
4307            cmd = mesonlib.substitute_values(kwargs['command'], values)
4308            mlog.log('Configuring', mlog.bold(output), 'with command')
4309            res = self.run_command_impl(node, cmd,  {}, True)
4310            if res.returncode != 0:
4311                raise InterpreterException('Running configure command failed.\n%s\n%s' %
4312                                           (res.stdout, res.stderr))
4313            if 'capture' in kwargs and kwargs['capture']:
4314                dst_tmp = ofile_abs + '~'
4315                file_encoding = kwargs.setdefault('encoding', 'utf-8')
4316                with open(dst_tmp, 'w', encoding=file_encoding) as f:
4317                    f.writelines(res.stdout)
4318                if inputs_abs:
4319                    shutil.copymode(inputs_abs[0], dst_tmp)
4320                mesonlib.replace_if_different(ofile_abs, dst_tmp)
4321            if depfile:
4322                mlog.log('Reading depfile:', mlog.bold(depfile))
4323                with open(depfile, 'r') as f:
4324                    df = DepFile(f.readlines())
4325                    deps = df.get_all_dependencies(ofile_fname)
4326                    for dep in deps:
4327                        self.add_build_def_file(dep)
4328
4329        elif 'copy' in kwargs:
4330            if len(inputs_abs) != 1:
4331                raise InterpreterException('Exactly one input file must be given in copy mode')
4332            os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
4333            shutil.copyfile(inputs_abs[0], ofile_abs)
4334            shutil.copystat(inputs_abs[0], ofile_abs)
4335        else:
4336            # Not reachable
4337            raise AssertionError
4338        # Install file if requested, we check for the empty string
4339        # for backwards compatibility. That was the behaviour before
4340        # 0.45.0 so preserve it.
4341        idir = kwargs.get('install_dir', '')
4342        if idir is False:
4343            idir = ''
4344            mlog.deprecation('Please use the new `install:` kwarg instead of passing '
4345                             '`false` to `install_dir:`', location=node)
4346        if not isinstance(idir, str):
4347            if isinstance(idir, list) and len(idir) == 0:
4348                mlog.deprecation('install_dir: kwarg must be a string and not an empty array. '
4349                                 'Please use the install: kwarg to enable or disable installation. '
4350                                 'This will be a hard error in the next release.')
4351            else:
4352                raise InterpreterException('"install_dir" must be a string')
4353        install = kwargs.get('install', idir != '')
4354        if not isinstance(install, bool):
4355            raise InterpreterException('"install" must be a boolean')
4356        if install:
4357            if not idir:
4358                raise InterpreterException('"install_dir" must be specified '
4359                                           'when "install" in a configure_file '
4360                                           'is true')
4361            cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname)
4362            install_mode = self._get_kwarg_install_mode(kwargs)
4363            self.build.data.append(build.Data([cfile], idir, install_mode))
4364        return mesonlib.File.from_built_file(self.subdir, output)
4365
4366    def extract_incdirs(self, kwargs):
4367        prospectives = unholder(extract_as_list(kwargs, 'include_directories'))
4368        result = []
4369        for p in prospectives:
4370            if isinstance(p, build.IncludeDirs):
4371                result.append(p)
4372            elif isinstance(p, str):
4373                result.append(self.build_incdir_object([p]).held_object)
4374            else:
4375                raise InterpreterException('Include directory objects can only be created from strings or include directories.')
4376        return result
4377
4378    @permittedKwargs(permitted_kwargs['include_directories'])
4379    @stringArgs
4380    def func_include_directories(self, node, args, kwargs):
4381        return self.build_incdir_object(args, kwargs.get('is_system', False))
4382
4383    def build_incdir_object(self, incdir_strings, is_system=False):
4384        if not isinstance(is_system, bool):
4385            raise InvalidArguments('Is_system must be boolean.')
4386        src_root = self.environment.get_source_dir()
4387        build_root = self.environment.get_build_dir()
4388        absbase_src = os.path.join(src_root, self.subdir)
4389        absbase_build = os.path.join(build_root, self.subdir)
4390
4391        for a in incdir_strings:
4392            if a.startswith(src_root):
4393                raise InvalidArguments('Tried to form an absolute path to a source dir. '
4394                                       'You should not do that but use relative paths instead.'
4395                                       '''
4396
4397To get include path to any directory relative to the current dir do
4398
4399incdir = include_directories(dirname)
4400
4401After this incdir will contain both the current source dir as well as the
4402corresponding build dir. It can then be used in any subdirectory and
4403Meson will take care of all the busywork to make paths work.
4404
4405Dirname can even be '.' to mark the current directory. Though you should
4406remember that the current source and build directories are always
4407put in the include directories by default so you only need to do
4408include_directories('.') if you intend to use the result in a
4409different subdirectory.
4410''')
4411            absdir_src = os.path.join(absbase_src, a)
4412            absdir_build = os.path.join(absbase_build, a)
4413            if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build):
4414                raise InvalidArguments('Include dir %s does not exist.' % a)
4415        i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system))
4416        return i
4417
4418    @permittedKwargs(permitted_kwargs['add_test_setup'])
4419    @stringArgs
4420    def func_add_test_setup(self, node, args, kwargs):
4421        if len(args) != 1:
4422            raise InterpreterException('Add_test_setup needs one argument for the setup name.')
4423        setup_name = args[0]
4424        if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None:
4425            raise InterpreterException('Setup name may only contain alphanumeric characters.')
4426        if ":" not in setup_name:
4427            setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name
4428        try:
4429            inp = unholder(extract_as_list(kwargs, 'exe_wrapper'))
4430            exe_wrapper = []
4431            for i in inp:
4432                if isinstance(i, str):
4433                    exe_wrapper.append(i)
4434                elif isinstance(i, dependencies.ExternalProgram):
4435                    if not i.found():
4436                        raise InterpreterException('Tried to use non-found executable.')
4437                    exe_wrapper += i.get_command()
4438                else:
4439                    raise InterpreterException('Exe wrapper can only contain strings or external binaries.')
4440        except KeyError:
4441            exe_wrapper = None
4442        gdb = kwargs.get('gdb', False)
4443        if not isinstance(gdb, bool):
4444            raise InterpreterException('Gdb option must be a boolean')
4445        timeout_multiplier = kwargs.get('timeout_multiplier', 1)
4446        if not isinstance(timeout_multiplier, int):
4447            raise InterpreterException('Timeout multiplier must be a number.')
4448        is_default = kwargs.get('is_default', False)
4449        if not isinstance(is_default, bool):
4450            raise InterpreterException('is_default option must be a boolean')
4451        if is_default:
4452            if self.build.test_setup_default_name is not None:
4453                raise InterpreterException('\'%s\' is already set as default. '
4454                                           'is_default can be set to true only once' % self.build.test_setup_default_name)
4455            self.build.test_setup_default_name = setup_name
4456        env = self.unpack_env_kwarg(kwargs)
4457        self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env)
4458
4459    @permittedKwargs(permitted_kwargs['add_global_arguments'])
4460    @stringArgs
4461    def func_add_global_arguments(self, node, args, kwargs):
4462        for_machine = self.machine_from_native_kwarg(kwargs)
4463        self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs)
4464
4465    @permittedKwargs(permitted_kwargs['add_global_link_arguments'])
4466    @stringArgs
4467    def func_add_global_link_arguments(self, node, args, kwargs):
4468        for_machine = self.machine_from_native_kwarg(kwargs)
4469        self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs)
4470
4471    @permittedKwargs(permitted_kwargs['add_project_arguments'])
4472    @stringArgs
4473    def func_add_project_arguments(self, node, args, kwargs):
4474        for_machine = self.machine_from_native_kwarg(kwargs)
4475        self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs)
4476
4477    @permittedKwargs(permitted_kwargs['add_project_link_arguments'])
4478    @stringArgs
4479    def func_add_project_link_arguments(self, node, args, kwargs):
4480        for_machine = self.machine_from_native_kwarg(kwargs)
4481        self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs)
4482
4483    def warn_about_builtin_args(self, args):
4484        warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic')
4485        optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os')
4486        for arg in args:
4487            if arg in warnargs:
4488                mlog.warning('Consider using the built-in warning_level option instead of using "{}".'.format(arg),
4489                             location=self.current_node)
4490            elif arg in optargs:
4491                mlog.warning('Consider using the built-in optimization level instead of using "{}".'.format(arg),
4492                             location=self.current_node)
4493            elif arg == '-g':
4494                mlog.warning('Consider using the built-in debug option instead of using "{}".'.format(arg),
4495                             location=self.current_node)
4496            elif arg == '-pipe':
4497                mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.",
4498                             location=self.current_node)
4499            elif arg.startswith('-fsanitize'):
4500                mlog.warning('Consider using the built-in option for sanitizers instead of using "{}".'.format(arg),
4501                             location=self.current_node)
4502            elif arg.startswith('-std=') or arg.startswith('/std:'):
4503                mlog.warning('Consider using the built-in option for language standard version instead of using "{}".'.format(arg),
4504                             location=self.current_node)
4505
4506    def add_global_arguments(self, node, argsdict, args, kwargs):
4507        if self.is_subproject():
4508            msg = 'Function \'{}\' cannot be used in subprojects because ' \
4509                  'there is no way to make that reliable.\nPlease only call ' \
4510                  'this if is_subproject() returns false. Alternatively, ' \
4511                  'define a variable that\ncontains your language-specific ' \
4512                  'arguments and add it to the appropriate *_args kwarg ' \
4513                  'in each target.'.format(node.func_name)
4514            raise InvalidCode(msg)
4515        frozen = self.project_args_frozen or self.global_args_frozen
4516        self.add_arguments(node, argsdict, frozen, args, kwargs)
4517
4518    def add_project_arguments(self, node, argsdict, args, kwargs):
4519        if self.subproject not in argsdict:
4520            argsdict[self.subproject] = {}
4521        self.add_arguments(node, argsdict[self.subproject],
4522                           self.project_args_frozen, args, kwargs)
4523
4524    def add_arguments(self, node, argsdict, args_frozen, args, kwargs):
4525        if args_frozen:
4526            msg = 'Tried to use \'{}\' after a build target has been declared.\n' \
4527                  'This is not permitted. Please declare all ' \
4528                  'arguments before your targets.'.format(node.func_name)
4529            raise InvalidCode(msg)
4530
4531        if 'language' not in kwargs:
4532            raise InvalidCode('Missing language definition in {}'.format(node.func_name))
4533
4534        self.warn_about_builtin_args(args)
4535
4536        for lang in mesonlib.stringlistify(kwargs['language']):
4537            lang = lang.lower()
4538            argsdict[lang] = argsdict.get(lang, []) + args
4539
4540    @noKwargs
4541    @noArgsFlattening
4542    def func_environment(self, node, args, kwargs):
4543        if len(args) > 1:
4544            raise InterpreterException('environment takes only one optional positional arguments')
4545        elif len(args) == 1:
4546            FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject)
4547            initial_values = args[0]
4548            if not isinstance(initial_values, dict) and not isinstance(initial_values, list):
4549                raise InterpreterException('environment first argument must be a dictionary or a list')
4550        else:
4551            initial_values = {}
4552        return EnvironmentVariablesHolder(initial_values)
4553
4554    @stringArgs
4555    @noKwargs
4556    def func_join_paths(self, node, args, kwargs):
4557        return self.join_path_strings(args)
4558
4559    def run(self):
4560        super().run()
4561        mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
4562        FeatureNew.report(self.subproject)
4563        FeatureDeprecated.report(self.subproject)
4564        if not self.is_subproject():
4565            self.print_extra_warnings()
4566        if self.subproject == '':
4567            self._print_summary()
4568
4569    def print_extra_warnings(self):
4570        # TODO cross compilation
4571        for c in self.coredata.compilers.host.values():
4572            if c.get_id() == 'clang':
4573                self.check_clang_asan_lundef()
4574                break
4575
4576    def check_clang_asan_lundef(self):
4577        if 'b_lundef' not in self.coredata.base_options:
4578            return
4579        if 'b_sanitize' not in self.coredata.base_options:
4580            return
4581        if (self.coredata.base_options['b_lundef'].value and
4582                self.coredata.base_options['b_sanitize'].value != 'none'):
4583            mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef.
4584This will probably not work.
4585Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value),
4586                         location=self.current_node)
4587
4588    def evaluate_subproject_info(self, path_from_source_root, subproject_dirname):
4589        depth = 0
4590        subproj_name = ''
4591        segs = PurePath(path_from_source_root).parts
4592        segs_spd = PurePath(subproject_dirname).parts
4593        while segs and segs[0] == segs_spd[0]:
4594            if len(segs_spd) == 1:
4595                subproj_name = segs[1]
4596                segs = segs[2:]
4597                depth += 1
4598            else:
4599                segs_spd = segs_spd[1:]
4600                segs = segs[1:]
4601        return (depth, subproj_name)
4602
4603    # Check that the indicated file is within the same subproject
4604    # as we currently are. This is to stop people doing
4605    # nasty things like:
4606    #
4607    # f = files('../../master_src/file.c')
4608    #
4609    # Note that this is validated only when the file
4610    # object is generated. The result can be used in a different
4611    # subproject than it is defined in (due to e.g. a
4612    # declare_dependency).
4613    def validate_within_subproject(self, subdir, fname):
4614        norm = os.path.normpath(os.path.join(subdir, fname))
4615        if os.path.isabs(norm):
4616            if not norm.startswith(self.environment.source_dir):
4617                # Grabbing files outside the source tree is ok.
4618                # This is for vendor stuff like:
4619                #
4620                # /opt/vendorsdk/src/file_with_license_restrictions.c
4621                return
4622            norm = os.path.relpath(norm, self.environment.source_dir)
4623            assert(not os.path.isabs(norm))
4624        (num_sps, sproj_name) = self.evaluate_subproject_info(norm, self.subproject_dir)
4625        plain_filename = os.path.basename(norm)
4626        if num_sps == 0:
4627            if not self.is_subproject():
4628                return
4629            raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
4630        if num_sps > 1:
4631            raise InterpreterException('Sandbox violation: Tried to grab file %s from a nested subproject.' % plain_filename)
4632        if sproj_name != self.subproject_directory_name:
4633            raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
4634
4635    def source_strings_to_files(self, sources):
4636        results = []
4637        mesonlib.check_direntry_issues(sources)
4638        if not isinstance(sources, list):
4639            sources = [sources]
4640        for s in sources:
4641            if isinstance(s, (mesonlib.File, GeneratedListHolder,
4642                              TargetHolder, CustomTargetIndexHolder,
4643                              GeneratedObjectsHolder)):
4644                pass
4645            elif isinstance(s, str):
4646                self.validate_within_subproject(self.subdir, s)
4647                s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s)
4648            else:
4649                raise InterpreterException('Source item is {!r} instead of '
4650                                           'string or File-type object'.format(s))
4651            results.append(s)
4652        return results
4653
4654    def add_target(self, name, tobj):
4655        if name == '':
4656            raise InterpreterException('Target name must not be empty.')
4657        if name.strip() == '':
4658            raise InterpreterException('Target name must not consist only of whitespace.')
4659        if name.startswith('meson-'):
4660            raise InvalidArguments("Target names starting with 'meson-' are reserved "
4661                                   "for Meson's internal use. Please rename.")
4662        if name in coredata.forbidden_target_names:
4663            raise InvalidArguments("Target name '%s' is reserved for Meson's "
4664                                   "internal use. Please rename." % name)
4665        # To permit an executable and a shared library to have the
4666        # same name, such as "foo.exe" and "libfoo.a".
4667        idname = tobj.get_id()
4668        if idname in self.build.targets:
4669            raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name)
4670        self.build.targets[idname] = tobj
4671        if idname not in self.coredata.target_guids:
4672            self.coredata.target_guids[idname] = str(uuid.uuid4()).upper()
4673
4674    @FeatureNew('both_libraries', '0.46.0')
4675    def build_both_libraries(self, node, args, kwargs):
4676        shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
4677
4678        # Check if user forces non-PIC static library.
4679        pic = True
4680        if 'pic' in kwargs:
4681            pic = kwargs['pic']
4682        elif 'b_staticpic' in self.environment.coredata.base_options:
4683            pic = self.environment.coredata.base_options['b_staticpic'].value
4684
4685        if pic:
4686            # Exclude sources from args and kwargs to avoid building them twice
4687            static_args = [args[0]]
4688            static_kwargs = kwargs.copy()
4689            static_kwargs['sources'] = []
4690            static_kwargs['objects'] = shared_holder.held_object.extract_all_objects()
4691        else:
4692            static_args = args
4693            static_kwargs = kwargs
4694
4695        static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder)
4696
4697        return BothLibrariesHolder(shared_holder, static_holder, self)
4698
4699    def build_library(self, node, args, kwargs):
4700        default_library = self.coredata.get_builtin_option('default_library', self.subproject)
4701        if default_library == 'shared':
4702            return self.build_target(node, args, kwargs, SharedLibraryHolder)
4703        elif default_library == 'static':
4704            return self.build_target(node, args, kwargs, StaticLibraryHolder)
4705        elif default_library == 'both':
4706            return self.build_both_libraries(node, args, kwargs)
4707        else:
4708            raise InterpreterException('Unknown default_library value: %s.', default_library)
4709
4710    def build_target(self, node, args, kwargs, targetholder):
4711        @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
4712        @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
4713        @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
4714        @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility'])
4715        def build_target_decorator_caller(self, node, args, kwargs):
4716            return True
4717
4718        build_target_decorator_caller(self, node, args, kwargs)
4719
4720        if not args:
4721            raise InterpreterException('Target does not have a name.')
4722        name, *sources = args
4723        for_machine = self.machine_from_native_kwarg(kwargs)
4724        if 'sources' in kwargs:
4725            sources += listify(kwargs['sources'])
4726        sources = self.source_strings_to_files(sources)
4727        objs = extract_as_list(kwargs, 'objects')
4728        kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
4729        kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
4730        if 'extra_files' in kwargs:
4731            ef = extract_as_list(kwargs, 'extra_files')
4732            kwargs['extra_files'] = self.source_strings_to_files(ef)
4733        self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources)
4734        if targetholder == ExecutableHolder:
4735            targetclass = build.Executable
4736        elif targetholder == SharedLibraryHolder:
4737            targetclass = build.SharedLibrary
4738        elif targetholder == SharedModuleHolder:
4739            targetclass = build.SharedModule
4740        elif targetholder == StaticLibraryHolder:
4741            targetclass = build.StaticLibrary
4742        elif targetholder == JarHolder:
4743            targetclass = build.Jar
4744        else:
4745            mlog.debug('Unknown target type:', str(targetholder))
4746            raise RuntimeError('Unreachable code')
4747        self.kwarg_strings_to_includedirs(kwargs)
4748
4749        # Filter out kwargs from other target types. For example 'soversion'
4750        # passed to library() when default_library == 'static'.
4751        kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
4752
4753        kwargs['include_directories'] = self.extract_incdirs(kwargs)
4754        target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
4755        target.project_version = self.project_version
4756
4757        if not self.environment.machines.matches_build_machine(for_machine):
4758            self.add_cross_stdlib_info(target)
4759        l = targetholder(target, self)
4760        self.add_target(name, l.held_object)
4761        self.project_args_frozen = True
4762        return l
4763
4764    def kwarg_strings_to_includedirs(self, kwargs):
4765        if 'd_import_dirs' in kwargs:
4766            items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
4767            cleaned_items = []
4768            for i in items:
4769                if isinstance(i, str):
4770                    # BW compatibility. This was permitted so we must support it
4771                    # for a few releases so people can transition to "correct"
4772                    # path declarations.
4773                    if os.path.normpath(i).startswith(self.environment.get_source_dir()):
4774                        mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead.
4775This will become a hard error in the future.''', location=self.current_node)
4776                        i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir))
4777                        i = self.build_incdir_object([i])
4778                cleaned_items.append(i)
4779            kwargs['d_import_dirs'] = cleaned_items
4780
4781    def get_used_languages(self, target):
4782        result = {}
4783        for i in target.sources:
4784            # TODO other platforms
4785            for lang, c in self.coredata.compilers.host.items():
4786                if c.can_compile(i):
4787                    result[lang] = True
4788                    break
4789        return result
4790
4791    def add_cross_stdlib_info(self, target):
4792        if target.for_machine != MachineChoice.HOST:
4793            return
4794        for l in self.get_used_languages(target):
4795            props = self.environment.properties.host
4796            if props.has_stdlib(l) \
4797                    and self.subproject != props.get_stdlib(l)[0]:
4798                target.add_deps(self.build.stdlibs.host[l])
4799
4800    def check_sources_exist(self, subdir, sources):
4801        for s in sources:
4802            if not isinstance(s, str):
4803                continue # This means a generated source and they always exist.
4804            fname = os.path.join(subdir, s)
4805            if not os.path.isfile(fname):
4806                raise InterpreterException('Tried to add non-existing source file %s.' % s)
4807
4808    # Only permit object extraction from the same subproject
4809    def validate_extraction(self, buildtarget: InterpreterObject) -> None:
4810        if not self.subdir.startswith(self.subproject_dir):
4811            if buildtarget.subdir.startswith(self.subproject_dir):
4812                raise InterpreterException('Tried to extract objects from a subproject target.')
4813        else:
4814            if not buildtarget.subdir.startswith(self.subproject_dir):
4815                raise InterpreterException('Tried to extract objects from the main project from a subproject.')
4816            if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
4817                raise InterpreterException('Tried to extract objects from a different subproject.')
4818
4819    def is_subproject(self):
4820        return self.subproject != ''
4821
4822    @noKwargs
4823    @noArgsFlattening
4824    def func_set_variable(self, node, args, kwargs):
4825        if len(args) != 2:
4826            raise InvalidCode('Set_variable takes two arguments.')
4827        varname, value = args
4828        self.set_variable(varname, value)
4829
4830    @noKwargs
4831    @noArgsFlattening
4832    def func_get_variable(self, node, args, kwargs):
4833        if len(args) < 1 or len(args) > 2:
4834            raise InvalidCode('Get_variable takes one or two arguments.')
4835        varname = args[0]
4836        if isinstance(varname, Disabler):
4837            return varname
4838        if not isinstance(varname, str):
4839            raise InterpreterException('First argument must be a string.')
4840        try:
4841            return self.variables[varname]
4842        except KeyError:
4843            pass
4844        if len(args) == 2:
4845            return args[1]
4846        raise InterpreterException('Tried to get unknown variable "%s".' % varname)
4847
4848    @stringArgs
4849    @noKwargs
4850    def func_is_variable(self, node, args, kwargs):
4851        if len(args) != 1:
4852            raise InvalidCode('Is_variable takes two arguments.')
4853        varname = args[0]
4854        return varname in self.variables
4855
4856    @staticmethod
4857    def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice:
4858        native = kwargs.get('native', False)
4859        if not isinstance(native, bool):
4860            raise InvalidArguments('Argument to "native" must be a boolean.')
4861        return MachineChoice.BUILD if native else MachineChoice.HOST
4862
4863    @FeatureNew('is_disabler', '0.52.0')
4864    @noKwargs
4865    def func_is_disabler(self, node, args, kwargs):
4866        if len(args) != 1:
4867            raise InvalidCode('Is_disabler takes one argument.')
4868        varname = args[0]
4869        return isinstance(varname, Disabler)
4870