files: # Copyright 2012-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from collections import OrderedDict
  from functools import lru_cache
  import enum
  import json
  import os
  import pickle
  import re
  import shlex
  import subprocess
  import textwrap
  import typing as T
  
  from .. import build
  from .. import dependencies
  from .. import mesonlib
  from .. import mlog
  from ..compilers import CompilerArgs, VisualStudioLikeCompiler
  from ..mesonlib import (
      File, MachineChoice, MesonException, OrderedSet, OptionOverrideProxy,
      classify_unity_sources, unholder
  )
  
  if T.TYPE_CHECKING:
      from ..interpreter import Interpreter
  
  
  class TestProtocol(enum.Enum):
  
      EXITCODE = 0
      TAP = 1
  
      @classmethod
      def from_str(cls, string: str) -> 'TestProtocol':
          if string == 'exitcode':
              return cls.EXITCODE
          elif string == 'tap':
              return cls.TAP
          raise MesonException('unknown test format {}'.format(string))
  
      def __str__(self) -> str:
          if self is self.EXITCODE:
              return 'exitcode'
          return 'tap'
  
  
  class CleanTrees:
      '''
      Directories outputted by custom targets that have to be manually cleaned
      because on Linux `ninja clean` only deletes empty directories.
      '''
      def __init__(self, build_dir, trees):
          self.build_dir = build_dir
          self.trees = trees
  
  class InstallData:
      def __init__(self, source_dir, build_dir, prefix, strip_bin,
                   install_umask, mesonintrospect):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.prefix = prefix
          self.strip_bin = strip_bin
          self.install_umask = install_umask
          self.targets = []
          self.headers = []
          self.man = []
          self.data = []
          self.po_package_name = ''
          self.po = []
          self.install_scripts = []
          self.install_subdirs = []
          self.mesonintrospect = mesonintrospect
  
  class TargetInstallData:
      def __init__(self, fname, outdir, aliases, strip, install_name_mappings, install_rpath, install_mode, optional=False):
          self.fname = fname
          self.outdir = outdir
          self.aliases = aliases
          self.strip = strip
          self.install_name_mappings = install_name_mappings
          self.install_rpath = install_rpath
          self.install_mode = install_mode
          self.optional = optional
  
  class ExecutableSerialisation:
      def __init__(self, cmd_args, env=None, exe_wrapper=None,
                   workdir=None, extra_paths=None, capture=None):
          self.cmd_args = cmd_args
          self.env = env or {}
          if exe_wrapper is not None:
              assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
          self.exe_runner = exe_wrapper
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.capture = capture
  
  class TestSerialisation:
      def __init__(self, name: str, project: str, suite: str, fname: T.List[str],
                   is_cross_built: bool, exe_wrapper: T.Optional[build.Executable],
                   needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str],
                   env: build.EnvironmentVariables, should_fail: bool,
                   timeout: T.Optional[int], workdir: T.Optional[str],
                   extra_paths: T.List[str], protocol: TestProtocol, priority: int):
          self.name = name
          self.project_name = project
          self.suite = suite
          self.fname = fname
          self.is_cross_built = is_cross_built
          if exe_wrapper is not None:
              assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
          self.exe_runner = exe_wrapper
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.protocol = protocol
          self.priority = priority
          self.needs_exe_wrapper = needs_exe_wrapper
  
  def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']:
      if backend == 'ninja':
          from . import ninjabackend
          return ninjabackend.NinjaBackend(build, interpreter)
      elif backend == 'vs':
          from . import vs2010backend
          return vs2010backend.autodetect_vs_version(build, interpreter)
      elif backend == 'vs2010':
          from . import vs2010backend
          return vs2010backend.Vs2010Backend(build, interpreter)
      elif backend == 'vs2015':
          from . import vs2015backend
          return vs2015backend.Vs2015Backend(build, interpreter)
      elif backend == 'vs2017':
          from . import vs2017backend
          return vs2017backend.Vs2017Backend(build, interpreter)
      elif backend == 'vs2019':
          from . import vs2019backend
          return vs2019backend.Vs2019Backend(build, interpreter)
      elif backend == 'xcode':
          from . import xcodebackend
          return xcodebackend.XCodeBackend(build, interpreter)
      return None
  
  # This class contains the basic functionality that is needed by all backends.
  # Feel free to move stuff in and out of it as you see fit.
  class Backend:
      def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']):
          # Make it possible to construct a dummy backend
          # This is used for introspection without a build directory
          if build is None:
              self.environment = None
              return
          self.build = build
          self.interpreter = interpreter
          self.environment = build.environment
          self.processed_targets = {}
          self.build_dir = self.environment.get_build_dir()
          self.source_dir = self.environment.get_source_dir()
          self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(),
                                               self.environment.get_build_dir())
  
      def get_target_filename(self, t):
          if isinstance(t, build.CustomTarget):
              if len(t.get_outputs()) != 1:
                  mlog.warning('custom_target {!r} has more than one output! '
                               'Using the first one.'.format(t.name))
              filename = t.get_outputs()[0]
          elif isinstance(t, build.CustomTargetIndex):
              filename = t.get_outputs()[0]
          else:
              assert(isinstance(t, build.BuildTarget))
              filename = t.get_filename()
          return os.path.join(self.get_target_dir(t), filename)
  
      def get_target_filename_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))
  
      def get_base_options_for_target(self, target):
          return OptionOverrideProxy(target.option_overrides_base,
                                     self.environment.coredata.builtins,
                                     self.environment.coredata.base_options)
  
      def get_compiler_options_for_target(self, target):
          comp_reg = self.environment.coredata.compiler_options[target.for_machine]
          comp_override = target.option_overrides_compiler
          return {
              lang: OptionOverrideProxy(comp_override[lang], comp_reg[lang])
              for lang in set(comp_reg.keys()) | set(comp_override.keys())
          }
  
      def get_option_for_target(self, option_name, target):
          if option_name in target.option_overrides_base:
              override = target.option_overrides_base[option_name]
              return self.environment.coredata.validate_option_value(option_name, override)
          return self.environment.coredata.get_builtin_option(option_name, target.subproject)
  
      def get_target_filename_for_linking(self, target):
          # On some platforms (msvc for instance), the file that is used for
          # dynamic linking is not the same as the dynamic library itself. This
          # file is called an import library, and we want to link against that.
          # On all other platforms, we link to the library directly.
          if isinstance(target, build.SharedLibrary):
              link_lib = target.get_import_filename() or target.get_filename()
              return os.path.join(self.get_target_dir(target), link_lib)
          elif isinstance(target, build.StaticLibrary):
              return os.path.join(self.get_target_dir(target), target.get_filename())
          elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)):
              if not target.is_linkable_target():
                  raise MesonException('Tried to link against custom target "%s", which is not linkable.' % target.name)
              return os.path.join(self.get_target_dir(target), target.get_filename())
          elif isinstance(target, build.Executable):
              if target.import_filename:
                  return os.path.join(self.get_target_dir(target), target.get_import_filename())
              else:
                  return None
          raise AssertionError('BUG: Tried to link to {!r} which is not linkable'.format(target))
  
      @lru_cache(maxsize=None)
      def get_target_dir(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              dirname = target.get_subdir()
          else:
              dirname = 'meson-out'
          return dirname
  
      def get_target_dir_relative_to(self, t, o):
          '''Get a target dir relative to another target's directory'''
          target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t))
          othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o))
          return os.path.relpath(target_dir, othert_dir)
  
      def get_target_source_dir(self, target):
          # if target dir is empty, avoid extraneous trailing / from os.path.join()
          target_dir = self.get_target_dir(target)
          if target_dir:
              return os.path.join(self.build_to_src, target_dir)
          return self.build_to_src
  
      def get_target_private_dir(self, target):
          return os.path.join(self.get_target_dir(target), target.get_id())
  
      def get_target_private_dir_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target))
  
      @lru_cache(maxsize=None)
      def get_target_generated_dir(self, target, gensrc, src):
          """
          Takes a BuildTarget, a generator source (CustomTarget or GeneratedList),
          and a generated source filename.
          Returns the full path of the generated source relative to the build root
          """
          # CustomTarget generators output to the build dir of the CustomTarget
          if isinstance(gensrc, (build.CustomTarget, build.CustomTargetIndex)):
              return os.path.join(self.get_target_dir(gensrc), src)
          # GeneratedList generators output to the private build directory of the
          # target that the GeneratedList is used in
          return os.path.join(self.get_target_private_dir(target), src)
  
      def get_unity_source_file(self, target, suffix, number):
          # There is a potential conflict here, but it is unlikely that
          # anyone both enables unity builds and has a file called foo-unity.cpp.
          osrc = '{}-unity{}.{}'.format(target.name, number, suffix)
          return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc)
  
      def generate_unity_files(self, target, unity_src):
          abs_files = []
          result = []
          compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
          unity_size = self.get_option_for_target('unity_size', target)
  
          def init_language_file(suffix, unity_file_number):
              unity_src = self.get_unity_source_file(target, suffix, unity_file_number)
              outfileabs = unity_src.absolute_path(self.environment.get_source_dir(),
                                                   self.environment.get_build_dir())
              outfileabs_tmp = outfileabs + '.tmp'
              abs_files.append(outfileabs)
              outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp)
              if not os.path.exists(outfileabs_tmp_dir):
                  os.makedirs(outfileabs_tmp_dir)
              result.append(unity_src)
              return open(outfileabs_tmp, 'w')
  
          # For each language, generate unity source files and return the list
          for comp, srcs in compsrcs.items():
              files_in_current = unity_size + 1
              unity_file_number = 0
              ofile = None
              for src in srcs:
                  if files_in_current >= unity_size:
                      if ofile:
                          ofile.close()
                      ofile = init_language_file(comp.get_default_suffix(), unity_file_number)
                      unity_file_number += 1
                      files_in_current = 0
                  ofile.write('#include<%s>\n' % src)
                  files_in_current += 1
              if ofile:
                  ofile.close()
  
          [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files]
          return result
  
      def relpath(self, todir, fromdir):
          return os.path.relpath(os.path.join('dummyprefixdir', todir),
                                 os.path.join('dummyprefixdir', fromdir))
  
      def flatten_object_list(self, target, proj_dir_to_build_root=''):
          obj_list = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root)
          return list(dict.fromkeys(obj_list))
  
      def _flatten_object_list(self, target, objects, proj_dir_to_build_root):
          obj_list = []
          for obj in objects:
              if isinstance(obj, str):
                  o = os.path.join(proj_dir_to_build_root,
                                   self.build_to_src, target.get_subdir(), obj)
                  obj_list.append(o)
              elif isinstance(obj, mesonlib.File):
                  obj_list.append(obj.rel_to_builddir(self.build_to_src))
              elif isinstance(obj, build.ExtractedObjects):
                  if obj.recursive:
                      obj_list += self._flatten_object_list(obj.target, obj.objlist, proj_dir_to_build_root)
                  obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root)
              else:
                  raise MesonException('Unknown data type in object list.')
          return obj_list
  
      def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None,
                               for_machine=MachineChoice.BUILD,
                               extra_bdeps=None, capture=None, force_serialize=False):
          '''
          Serialize an executable for running with a generator or a custom target
          '''
          import hashlib
          machine = self.environment.machines[for_machine]
          if machine.is_windows() or machine.is_cygwin():
              extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or [])
          else:
              extra_paths = []
  
          if isinstance(exe, dependencies.ExternalProgram):
              exe_cmd = exe.get_command()
              exe_for_machine = exe.for_machine
          elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
              exe_cmd = [self.get_target_filename_abs(exe)]
              exe_for_machine = exe.for_machine
          else:
              exe_cmd = [exe]
              exe_for_machine = MachineChoice.BUILD
  
          is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine)
          if is_cross_built and self.environment.need_exe_wrapper():
              exe_wrapper = self.environment.get_exe_wrapper()
              if not exe_wrapper.found():
                  msg = 'The exe_wrapper {!r} defined in the cross file is ' \
                        'needed by target {!r}, but was not found. Please ' \
                        'check the command and/or add it to PATH.'
                  raise MesonException(msg.format(exe_wrapper.name, tname))
          else:
              if exe_cmd[0].endswith('.jar'):
                  exe_cmd = ['java', '-jar'] + exe_cmd
              elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin()):
                  exe_cmd = ['mono'] + exe_cmd
              exe_wrapper = None
  
          force_serialize = force_serialize or extra_paths or workdir or \
              exe_wrapper or any('\n' in c for c in cmd_args)
          if not force_serialize:
              if not capture:
                  return None
              return (self.environment.get_build_command() +
                      ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args)
  
          workdir = workdir or self.environment.get_build_dir()
          env = {}
          if isinstance(exe, (dependencies.ExternalProgram,
                              build.BuildTarget, build.CustomTarget)):
              basename = exe.name
          else:
              basename = os.path.basename(exe)
  
          # Can't just use exe.name here; it will likely be run more than once
          # Take a digest of the cmd args, env, workdir, and capture. This avoids
          # collisions and also makes the name deterministic over regenerations
          # which avoids a rebuild by Ninja because the cmdline stays the same.
          data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture),
                       encoding='utf-8')
          digest = hashlib.sha1(data).hexdigest()
          scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest)
          exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
          with open(exe_data, 'wb') as f:
              es = ExecutableSerialisation(exe_cmd + cmd_args, env,
                                           exe_wrapper, workdir,
                                           extra_paths, capture)
              pickle.dump(es, f)
          return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data]
  
      def serialize_tests(self):
          test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat')
          with open(test_data, 'wb') as datafile:
              self.write_test_file(datafile)
          benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat')
          with open(benchmark_data, 'wb') as datafile:
              self.write_benchmark_file(datafile)
          return test_data, benchmark_data
  
      def determine_linker_and_stdlib_args(self, target):
          '''
          If we're building a static library, there is only one static linker.
          Otherwise, we query the target for the dynamic linker.
          '''
          if isinstance(target, build.StaticLibrary):
              return self.build.static_linker[target.for_machine], []
          l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs()
          return l, stdlib_args
  
      @staticmethod
      def _libdir_is_system(libdir, compilers, env):
          libdir = os.path.normpath(libdir)
          for cc in compilers.values():
              if libdir in cc.get_library_dirs(env):
                  return True
          return False
  
      def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True):
          paths = []
          for dep in target.external_deps:
              if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)):
                  continue
              la = dep.link_args
              if len(la) != 1 or not os.path.isabs(la[0]):
                  continue
              # The only link argument is an absolute path to a library file.
              libpath = la[0]
              libdir = os.path.dirname(libpath)
              if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
                  # No point in adding system paths.
                  continue
              # Windows doesn't support rpaths, but we use this function to
              # emulate rpaths by setting PATH, so also accept DLLs here
              if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']:
                  continue
              if libdir.startswith(self.environment.get_source_dir()):
                  rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:]
                  assert not os.path.isabs(rel_to_src), 'rel_to_src: {} is absolute'.format(rel_to_src)
                  paths.append(os.path.join(self.build_to_src, rel_to_src))
              else:
                  paths.append(libdir)
          return paths
  
      def determine_rpath_dirs(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              result = target.get_link_dep_subdirs()
          else:
              result = OrderedSet()
              result.add('meson-out')
          result.update(self.rpaths_for_bundled_shared_libraries(target))
          return tuple(result)
  
      def object_filename_from_source(self, target, source):
          assert isinstance(source, mesonlib.File)
          build_dir = self.environment.get_build_dir()
          rel_src = source.rel_to_builddir(self.build_to_src)
  
          # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o
          if rel_src.endswith(('.vala', '.gs')):
              # See description in generate_vala_compile for this logic.
              if source.is_built:
                  if os.path.isabs(rel_src):
                      rel_src = rel_src[len(build_dir) + 1:]
                  rel_src = os.path.relpath(rel_src, self.get_target_private_dir(target))
              else:
                  rel_src = os.path.basename(rel_src)
              # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
              source = 'meson-generated_' + rel_src[:-5] + '.c'
          elif source.is_built:
              if os.path.isabs(rel_src):
                  rel_src = rel_src[len(build_dir) + 1:]
              targetdir = self.get_target_private_dir(target)
              # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
              source = 'meson-generated_' + os.path.relpath(rel_src, targetdir)
          else:
              if os.path.isabs(rel_src):
                  # Use the absolute path directly to avoid file name conflicts
                  source = rel_src
              else:
                  source = os.path.relpath(os.path.join(build_dir, rel_src),
                                           os.path.join(self.environment.get_source_dir(), target.get_subdir()))
          machine = self.environment.machines[target.for_machine]
          return source.replace('/', '_').replace('\\', '_') + '.' + machine.get_object_suffix()
  
      def determine_ext_objs(self, extobj, proj_dir_to_build_root):
          result = []
  
          # Merge sources and generated sources
          sources = list(extobj.srclist)
          for gensrc in extobj.genlist:
              for s in gensrc.get_outputs():
                  path = self.get_target_generated_dir(extobj.target, gensrc, s)
                  dirpart, fnamepart = os.path.split(path)
                  sources.append(File(True, dirpart, fnamepart))
  
          # Filter out headers and all non-source files
          filtered_sources = []
          for s in sources:
              if self.environment.is_source(s) and not self.environment.is_header(s):
                  filtered_sources.append(s)
              elif self.environment.is_object(s):
                  result.append(s.relative_name())
          sources = filtered_sources
  
          # extobj could contain only objects and no sources
          if not sources:
              return result
  
          targetdir = self.get_target_private_dir(extobj.target)
  
          # With unity builds, sources don't map directly to objects,
          # we only support extracting all the objects in this mode,
          # so just return all object files.
          if self.is_unity(extobj.target):
              compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources)
              sources = []
              unity_size = self.get_option_for_target('unity_size', extobj.target)
              for comp, srcs in compsrcs.items():
                  for i in range(len(srcs) // unity_size + 1):
                      osrc = self.get_unity_source_file(extobj.target,
                                                        comp.get_default_suffix(), i)
                      sources.append(osrc)
  
          for osrc in sources:
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              result.append(objpath)
  
          return result
  
      def get_pch_include_args(self, compiler, target):
          args = []
          pchpath = self.get_target_private_dir(target)
          includeargs = compiler.get_include_args(pchpath, False)
          p = target.get_pch(compiler.get_language())
          if p:
              args += compiler.get_pch_use_args(pchpath, p[0])
          return includeargs + args
  
      def create_msvc_pch_implementation(self, target, lang, pch_header):
          # We have to include the language in the file name, otherwise
          # pch.c and pch.cpp will both end up as pch.obj in VS backends.
          impl_name = 'meson_pch-%s.%s' % (lang, lang)
          pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name)
          # Make sure to prepend the build dir, since the working directory is
          # not defined. Otherwise, we might create the file in the wrong path.
          pch_file = os.path.join(self.build_dir, pch_rel_to_build)
          os.makedirs(os.path.dirname(pch_file), exist_ok=True)
  
          content = '#include "%s"' % os.path.basename(pch_header)
          pch_file_tmp = pch_file + '.tmp'
          with open(pch_file_tmp, 'w') as f:
              f.write(content)
          mesonlib.replace_if_different(pch_file, pch_file_tmp)
          return pch_rel_to_build
  
      @staticmethod
      def escape_extra_args(compiler, args):
          # No extra escaping/quoting needed when not running on Windows
          if not mesonlib.is_windows():
              return args
          extra_args = []
          # Compiler-specific escaping is needed for -D args but not for any others
          if isinstance(compiler, VisualStudioLikeCompiler):
              # MSVC needs escaping when a -D argument ends in \ or \"
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      # Without extra escaping for these two, the next character
                      # gets eaten
                      if arg.endswith('\\'):
                          arg += '\\'
                      elif arg.endswith('\\"'):
                          arg = arg[:-2] + '\\\\"'
                  extra_args.append(arg)
          else:
              # MinGW GCC needs all backslashes in defines to be doubly-escaped
              # FIXME: Not sure about Cygwin or Clang
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      arg = arg.replace('\\', '\\\\')
                  extra_args.append(arg)
          return extra_args
  
      def generate_basic_compiler_args(self, target, compiler, no_warn_args=False):
          # Create an empty commands list, and start adding arguments from
          # various sources in the order in which they must override each other
          # starting from hard-coded defaults followed by build options and so on.
          commands = CompilerArgs(compiler)
  
          copt_proxy = self.get_compiler_options_for_target(target)[compiler.language]
          # First, the trivial ones that are impossible to override.
          #
          # Add -nostdinc/-nostdinc++ if needed; can't be overridden
          commands += self.get_cross_stdlib_args(target, compiler)
          # Add things like /NOLOGO or -pipe; usually can't be overridden
          commands += compiler.get_always_args()
          # Only add warning-flags by default if the buildtype enables it, and if
          # we weren't explicitly asked to not emit warnings (for Vala, f.ex)
          if no_warn_args:
              commands += compiler.get_no_warn_args()
          elif self.get_option_for_target('buildtype', target) != 'plain':
              commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target))
          # Add -Werror if werror=true is set in the build options set on the
          # command-line or default_options inside project(). This only sets the
          # action to be done for warnings if/when they are emitted, so it's ok
          # to set it after get_no_warn_args() or get_warn_args().
          if self.get_option_for_target('werror', target):
              commands += compiler.get_werror_args()
          # Add compile args for c_* or cpp_* build options set on the
          # command-line or default_options inside project().
          commands += compiler.get_option_compile_args(copt_proxy)
          # Add buildtype args: optimization level, debugging, etc.
          commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target))
          commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target))
          commands += compiler.get_debug_args(self.get_option_for_target('debug', target))
          # Add compile args added using add_project_arguments()
          commands += self.build.get_project_args(compiler, target.subproject, target.for_machine)
          # Add compile args added using add_global_arguments()
          # These override per-project arguments
          commands += self.build.get_global_args(compiler, target.for_machine)
          # Compile args added from the env: CFLAGS/CXXFLAGS, etc, or the cross
          # file. We want these to override all the defaults, but not the
          # per-target compile args.
          commands += self.environment.coredata.get_external_args(target.for_machine, compiler.get_language())
          # Always set -fPIC for shared libraries
          if isinstance(target, build.SharedLibrary):
              commands += compiler.get_pic_args()
          # Set -fPIC for static libraries by default unless explicitly disabled
          if isinstance(target, build.StaticLibrary) and target.pic:
              commands += compiler.get_pic_args()
          if isinstance(target, build.Executable) and target.pie:
              commands += compiler.get_pie_args()
          # Add compile args needed to find external dependencies. Link args are
          # added while generating the link command.
          # NOTE: We must preserve the order in which external deps are
          # specified, so we reverse the list before iterating over it.
          for dep in reversed(target.get_external_deps()):
              if not dep.found():
                  continue
  
              if compiler.language == 'vala':
                  if isinstance(dep, dependencies.PkgConfigDependency):
                      if dep.name == 'glib-2.0' and dep.version_reqs is not None:
                          for req in dep.version_reqs:
                              if req.startswith(('>=', '==')):
                                  commands += ['--target-glib', req[2:]]
                                  break
                      commands += ['--pkg', dep.name]
                  elif isinstance(dep, dependencies.ExternalLibrary):
                      commands += dep.get_link_args('vala')
              else:
                  commands += compiler.get_dependency_compile_args(dep)
              # Qt needs -fPIC for executables
              # XXX: We should move to -fPIC for all executables
              if isinstance(target, build.Executable):
                  commands += dep.get_exe_args(compiler)
              # For 'automagic' deps: Boost and GTest. Also dependency('threads').
              # pkg-config puts the thread flags itself via `Cflags:`
          # Fortran requires extra include directives.
          if compiler.language == 'fortran':
              for lt in target.link_targets:
                  priv_dir = self.get_target_private_dir(lt)
                  commands += compiler.get_include_args(priv_dir, False)
          return commands
  
      def build_target_link_arguments(self, compiler, deps):
          args = []
          for d in deps:
              if not (d.is_linkable_target()):
                  raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename())
              arg = self.get_target_filename_for_linking(d)
              if not arg:
                  continue
              if compiler.get_language() == 'd':
                  arg = '-Wl,' + arg
              else:
                  arg = compiler.get_linker_lib_prefix() + arg
              args.append(arg)
          return args
  
      def get_mingw_extra_paths(self, target):
          paths = OrderedSet()
          # The cross bindir
          root = self.environment.properties[target.for_machine].get_root()
          if root:
              paths.add(os.path.join(root, 'bin'))
          # The toolchain bindir
          sys_root = self.environment.properties[target.for_machine].get_sys_root()
          if sys_root:
              paths.add(os.path.join(sys_root, 'bin'))
          # Get program and library dirs from all target compilers
          if isinstance(target, build.BuildTarget):
              for cc in target.compilers.values():
                  paths.update(cc.get_program_dirs(self.environment))
                  paths.update(cc.get_library_dirs(self.environment))
          return list(paths)
  
      def determine_windows_extra_paths(self, target: T.Union[build.BuildTarget, str], extra_bdeps):
          '''On Windows there is no such thing as an rpath.
          We must determine all locations of DLLs that this exe
          links to and return them so they can be used in unit
          tests.'''
          result = set()
          prospectives = set()
          if isinstance(target, build.BuildTarget):
              prospectives.update(target.get_transitive_link_deps())
              # External deps
              for deppath in self.rpaths_for_bundled_shared_libraries(target, exclude_system=False):
                  result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath)))
          for bdep in extra_bdeps:
              prospectives.update(bdep.get_transitive_link_deps())
          # Internal deps
          for ld in prospectives:
              if ld == '' or ld == '.':
                  continue
              dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld))
              result.add(dirseg)
          if (isinstance(target, build.BuildTarget) and
                  not self.environment.machines.matches_build_machine(target.for_machine)):
              result.update(self.get_mingw_extra_paths(target))
          return list(result)
  
      def write_benchmark_file(self, datafile):
          self.write_test_serialisation(self.build.get_benchmarks(), datafile)
  
      def write_test_file(self, datafile):
          self.write_test_serialisation(self.build.get_tests(), datafile)
  
      def create_test_serialisation(self, tests):
          arr = []
          for t in sorted(tests, key=lambda tst: -1 * tst.priority):
              exe = t.get_exe()
              if isinstance(exe, dependencies.ExternalProgram):
                  cmd = exe.get_command()
              else:
                  cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
              if isinstance(exe, (build.BuildTarget, dependencies.ExternalProgram)):
                  test_for_machine = exe.for_machine
              else:
                  # E.g. an external verifier or simulator program run on a generated executable.
                  # Can always be run without a wrapper.
                  test_for_machine = MachineChoice.BUILD
              is_cross = not self.environment.machines.matches_build_machine(test_for_machine)
              if is_cross and self.environment.need_exe_wrapper():
                  exe_wrapper = self.environment.get_exe_wrapper()
              else:
                  exe_wrapper = None
              machine = self.environment.machines[exe.for_machine]
              if machine.is_windows() or machine.is_cygwin():
                  extra_bdeps = []
                  if isinstance(exe, build.CustomTarget):
                      extra_bdeps = exe.get_transitive_build_target_deps()
                  extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps)
              else:
                  extra_paths = []
              cmd_args = []
              for a in unholder(t.cmd_args):
                  if isinstance(a, build.BuildTarget):
                      extra_paths += self.determine_windows_extra_paths(a, [])
                  if isinstance(a, mesonlib.File):
                      a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
                      cmd_args.append(a)
                  elif isinstance(a, str):
                      cmd_args.append(a)
                  elif isinstance(a, build.Target):
                      cmd_args.append(self.construct_target_rel_path(a, t.workdir))
                  else:
                      raise MesonException('Bad object in test command.')
              ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross,
                                     exe_wrapper, self.environment.need_exe_wrapper(),
                                     t.is_parallel, cmd_args, t.env,
                                     t.should_fail, t.timeout, t.workdir,
                                     extra_paths, t.protocol, t.priority)
              arr.append(ts)
          return arr
  
      def write_test_serialisation(self, tests, datafile):
          pickle.dump(self.create_test_serialisation(tests), datafile)
  
      def construct_target_rel_path(self, a, workdir):
          if workdir is None:
              return self.get_target_filename(a)
          assert(os.path.isabs(workdir))
          abs_path = self.get_target_filename_abs(a)
          return os.path.relpath(abs_path, workdir)
  
      def generate_depmf_install(self, d):
          if self.build.dep_manifest_name is None:
              return
          ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
          ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
          mfobj = {'type': 'dependency manifest', 'version': '1.0', 'projects': self.build.dep_manifest}
          with open(ifilename, 'w') as f:
              f.write(json.dumps(mfobj))
          # Copy file from, to, and with mode unchanged
          d.data.append([ifilename, ofilename, None])
  
      def get_regen_filelist(self):
          '''List of all files whose alteration means that the build
          definition needs to be regenerated.'''
          deps = [os.path.join(self.build_to_src, df)
                  for df in self.interpreter.get_build_def_files()]
          if self.environment.is_cross_build():
              deps.extend(self.environment.coredata.cross_files)
          deps.extend(self.environment.coredata.config_files)
          deps.append('meson-private/coredata.dat')
          self.check_clock_skew(deps)
          return deps
  
      def check_clock_skew(self, file_list):
          # If a file that leads to reconfiguration has a time
          # stamp in the future, it will trigger an eternal reconfigure
          # loop.
          import time
          now = time.time()
          for f in file_list:
              absf = os.path.join(self.environment.get_build_dir(), f)
              ftime = os.path.getmtime(absf)
              delta = ftime - now
              # On Windows disk time stamps sometimes point
              # to the future by a minuscule amount, less than
              # 0.001 seconds. I don't know why.
              if delta > 0.001:
                  raise MesonException('Clock skew detected. File {} has a time stamp {:.4f}s in the future.'.format(absf, delta))
  
      def build_target_to_cmd_array(self, bt):
          if isinstance(bt, build.BuildTarget):
              if isinstance(bt, build.Executable) and bt.for_machine is not MachineChoice.BUILD:
                  if (self.environment.is_cross_build() and
                          self.environment.exe_wrapper is None and
                          self.environment.need_exe_wrapper()):
                      s = textwrap.dedent('''
                          Cannot use target {} as a generator because it is built for the
                          host machine and no exe wrapper is defined or needs_exe_wrapper is
                          true. You might want to set `native: true` instead to build it for
                          the build machine.'''.format(bt.name))
                      raise MesonException(s)
              arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))]
          else:
              arr = bt.get_command()
          return arr
  
      def replace_extra_args(self, args, genlist):
          final_args = []
          for a in args:
              if a == '@EXTRA_ARGS@':
                  final_args += genlist.get_extra_args()
              else:
                  final_args.append(a)
          return final_args
  
      def replace_outputs(self, args, private_dir, output_list):
          newargs = []
          regex = re.compile(r'@OUTPUT(\d+)@')
          for arg in args:
              m = regex.search(arg)
              while m is not None:
                  index = int(m.group(1))
                  src = '@OUTPUT%d@' % index
                  arg = arg.replace(src, os.path.join(private_dir, output_list[index]))
                  m = regex.search(arg)
              newargs.append(arg)
          return newargs
  
      def get_build_by_default_targets(self):
          result = OrderedDict()
          # Get all build and custom targets that must be built by default
          for name, t in self.build.get_targets().items():
              if t.build_by_default:
                  result[name] = t
          # Get all targets used as test executables and arguments. These must
          # also be built by default. XXX: Sometime in the future these should be
          # built only before running tests.
          for t in self.build.get_tests():
              exe = unholder(t.exe)
              if isinstance(exe, (build.CustomTarget, build.BuildTarget)):
                  result[exe.get_id()] = exe
              for arg in unholder(t.cmd_args):
                  if not isinstance(arg, (build.CustomTarget, build.BuildTarget)):
                      continue
                  result[arg.get_id()] = arg
              for dep in t.depends:
                  assert isinstance(dep, (build.CustomTarget, build.BuildTarget))
                  result[dep.get_id()] = dep
          return result
  
      @lru_cache(maxsize=None)
      def get_custom_target_provided_by_generated_source(self, generated_source):
          libs = []
          for f in generated_source.get_outputs():
              if self.environment.is_library(f):
                  libs.append(os.path.join(self.get_target_dir(generated_source), f))
          return libs
  
      @lru_cache(maxsize=None)
      def get_custom_target_provided_libraries(self, target):
          libs = []
          for t in target.get_generated_sources():
              if not isinstance(t, build.CustomTarget):
                  continue
              l = self.get_custom_target_provided_by_generated_source(t)
              libs = libs + l
          return libs
  
      def is_unity(self, target):
          optval = self.get_option_for_target('unity', target)
          if optval == 'on' or (optval == 'subprojects' and target.subproject != ''):
              return True
          return False
  
      def get_custom_target_sources(self, target):
          '''
          Custom target sources can be of various object types; strings, File,
          BuildTarget, even other CustomTargets.
          Returns the path to them relative to the build root directory.
          '''
          srcs = []
          for i in unholder(target.get_sources()):
              if isinstance(i, str):
                  fname = [os.path.join(self.build_to_src, target.subdir, i)]
              elif isinstance(i, build.BuildTarget):
                  fname = [self.get_target_filename(i)]
              elif isinstance(i, (build.CustomTarget, build.CustomTargetIndex)):
                  fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
              elif isinstance(i, build.GeneratedList):
                  fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()]
              elif isinstance(i, build.ExtractedObjects):
                  fname = [os.path.join(self.get_target_private_dir(i.target), p) for p in i.get_outputs(self)]
              else:
                  fname = [i.rel_to_builddir(self.build_to_src)]
              if target.absolute_paths:
                  fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
              srcs += fname
          return srcs
  
      def get_custom_target_depend_files(self, target, absolute_paths=False):
          deps = []
          for i in target.depend_files:
              if isinstance(i, mesonlib.File):
                  if absolute_paths:
                      deps.append(i.absolute_path(self.environment.get_source_dir(),
                                                  self.environment.get_build_dir()))
                  else:
                      deps.append(i.rel_to_builddir(self.build_to_src))
              else:
                  if absolute_paths:
                      deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i))
                  else:
                      deps.append(os.path.join(self.build_to_src, target.subdir, i))
          return deps
  
      def eval_custom_target_command(self, target, absolute_outputs=False):
          # We want the outputs to be absolute only when using the VS backend
          # XXX: Maybe allow the vs backend to use relative paths too?
          source_root = self.build_to_src
          build_root = '.'
          outdir = self.get_target_dir(target)
          if absolute_outputs:
              source_root = self.environment.get_source_dir()
              build_root = self.environment.get_build_dir()
              outdir = os.path.join(self.environment.get_build_dir(), outdir)
          outputs = []
          for i in target.get_outputs():
              outputs.append(os.path.join(outdir, i))
          inputs = self.get_custom_target_sources(target)
          # Evaluate the command list
          cmd = []
          for i in target.command:
              if isinstance(i, build.BuildTarget):
                  cmd += self.build_target_to_cmd_array(i)
                  continue
              elif isinstance(i, build.CustomTarget):
                  # GIR scanner will attempt to execute this binary but
                  # it assumes that it is in path, so always give it a full path.
                  tmp = i.get_outputs()[0]
                  i = os.path.join(self.get_target_dir(i), tmp)
              elif isinstance(i, mesonlib.File):
                  i = i.rel_to_builddir(self.build_to_src)
                  if target.absolute_paths:
                      i = os.path.join(self.environment.get_build_dir(), i)
              # FIXME: str types are blindly added ignoring 'target.absolute_paths'
              # because we can't know if they refer to a file or just a string
              elif not isinstance(i, str):
                  err_msg = 'Argument {0} is of unknown type {1}'
                  raise RuntimeError(err_msg.format(str(i), str(type(i))))
              elif '@SOURCE_ROOT@' in i:
                  i = i.replace('@SOURCE_ROOT@', source_root)
              elif '@BUILD_ROOT@' in i:
                  i = i.replace('@BUILD_ROOT@', build_root)
              elif '@DEPFILE@' in i:
                  if target.depfile is None:
                      msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
                            'keyword argument.'.format(target.name)
                      raise MesonException(msg)
                  dfilename = os.path.join(outdir, target.depfile)
                  i = i.replace('@DEPFILE@', dfilename)
              elif '@PRIVATE_DIR@' in i:
                  if target.absolute_paths:
                      pdir = self.get_target_private_dir_abs(target)
                  else:
                      pdir = self.get_target_private_dir(target)
                  i = i.replace('@PRIVATE_DIR@', pdir)
              elif '@PRIVATE_OUTDIR_' in i:
                  match = re.search(r'@PRIVATE_OUTDIR_(ABS_)?([^/\s*]*)@', i)
                  if not match:
                      msg = 'Custom target {!r} has an invalid argument {!r}' \
                            ''.format(target.name, i)
                      raise MesonException(msg)
                  source = match.group(0)
                  if match.group(1) is None and not target.absolute_paths:
                      lead_dir = ''
                  else:
                      lead_dir = self.environment.get_build_dir()
                  i = i.replace(source, os.path.join(lead_dir, outdir))
              cmd.append(i)
          # Substitute the rest of the template strings
          values = mesonlib.get_filenames_templates_dict(inputs, outputs)
          cmd = mesonlib.substitute_values(cmd, values)
          # This should not be necessary but removing it breaks
          # building GStreamer on Windows. The underlying issue
          # is problems with quoting backslashes on Windows
          # which is the seventh circle of hell. The downside is
          # that this breaks custom targets whose command lines
          # have backslashes. If you try to fix this be sure to
          # check that it does not break GST.
          #
          # The bug causes file paths such as c:\foo to get escaped
          # into c:\\foo.
          #
          # Unfortunately we have not been able to come up with an
          # isolated test case for this so unless you manage to come up
          # with one, the only way is to test the building with Gst's
          # setup. Note this in your MR or ping us and we will get it
          # fixed.
          #
          # https://github.com/mesonbuild/meson/pull/737
          cmd = [i.replace('\\', '/') for i in cmd]
          return inputs, outputs, cmd
  
      def run_postconf_scripts(self):
          env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
                 'MESON_BUILD_ROOT': self.environment.get_build_dir(),
                 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]),
                 }
          child_env = os.environ.copy()
          child_env.update(env)
  
          for s in self.build.postconf_scripts:
              cmd = s['exe'] + s['args']
              subprocess.check_call(cmd, env=child_env)
  
      def create_install_data(self):
          strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip')
          if strip_bin is None:
              if self.environment.is_cross_build():
                  mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
              else:
                  # TODO go through all candidates, like others
                  strip_bin = [self.environment.default_strip[0]]
          d = InstallData(self.environment.get_source_dir(),
                          self.environment.get_build_dir(),
                          self.environment.get_prefix(),
                          strip_bin,
                          self.environment.coredata.get_builtin_option('install_umask'),
                          self.environment.get_build_command() + ['introspect'])
          self.generate_depmf_install(d)
          self.generate_target_install(d)
          self.generate_header_install(d)
          self.generate_man_install(d)
          self.generate_data_install(d)
          self.generate_custom_install_script(d)
          self.generate_subdir_install(d)
          return d
  
      def create_install_data_files(self):
          install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
          with open(install_data_file, 'wb') as ofile:
              pickle.dump(self.create_install_data(), ofile)
  
      def generate_target_install(self, d):
          for t in self.build.get_targets().values():
              if not t.should_install():
                  continue
              outdirs, custom_install_dir = t.get_install_dir(self.environment)
              # Sanity-check the outputs and install_dirs
              num_outdirs, num_out = len(outdirs), len(t.get_outputs())
              if num_outdirs != 1 and num_outdirs != num_out:
                  m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
                      "Pass 'false' for outputs that should not be installed and 'true' for\n" \
                      'using the default installation directory for an output.'
                  raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs))
              install_mode = t.get_custom_install_mode()
              # Install the target output(s)
              if isinstance(t, build.BuildTarget):
                  # In general, stripping static archives is tricky and full of pitfalls.
                  # Wholesale stripping of static archives with a command such as
                  #
                  #   strip libfoo.a
                  #
                  # is broken, as GNU's strip will remove *every* symbol in a static
                  # archive. One solution to this nonintuitive behaviour would be
                  # to only strip local/debug symbols. Unfortunately, strip arguments
                  # are not specified by POSIX and therefore not portable. GNU's `-g`
                  # option (i.e. remove debug symbols) is equivalent to Apple's `-S`.
                  #
                  # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more
                  #       fine-grained stripping of static archives.
                  should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target('strip', t)
                  # Install primary build output (library/executable/jar, etc)
                  # Done separately because of strip/aliases/rpath
                  if outdirs[0] is not False:
                      mappings = t.get_link_deps_mapping(d.prefix, self.environment)
                      i = TargetInstallData(self.get_target_filename(t), outdirs[0],
                                            t.get_aliases(), should_strip, mappings,
                                            t.install_rpath, install_mode)
                      d.targets.append(i)
  
                      if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)):
                          # On toolchains/platforms that use an import library for
                          # linking (separate from the shared library with all the
                          # code), we need to install that too (dll.a/.lib).
                          if t.get_import_filename():
                              if custom_install_dir:
                                  # If the DLL is installed into a custom directory,
                                  # install the import library into the same place so
                                  # it doesn't go into a surprising place
                                  implib_install_dir = outdirs[0]
                              else:
                                  implib_install_dir = self.environment.get_import_lib_dir()
                              # Install the import library; may not exist for shared modules
                              i = TargetInstallData(self.get_target_filename_for_linking(t),
                                                    implib_install_dir, {}, False, {}, '', install_mode,
                                                    optional=isinstance(t, build.SharedModule))
                              d.targets.append(i)
  
                          if not should_strip and t.get_debug_filename():
                              debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename())
                              i = TargetInstallData(debug_file, outdirs[0],
                                                    {}, False, {}, '',
                                                    install_mode, optional=True)
                              d.targets.append(i)
                  # Install secondary outputs. Only used for Vala right now.
                  if num_outdirs > 1:
                      for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]):
                          # User requested that we not install this output
                          if outdir is False:
                              continue
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode)
                          d.targets.append(i)
              elif isinstance(t, build.CustomTarget):
                  # If only one install_dir is specified, assume that all
                  # outputs will be installed into it. This is for
                  # backwards-compatibility and because it makes sense to
                  # avoid repetition since this is a common use-case.
                  #
                  # To selectively install only some outputs, pass `false` as
                  # the install_dir for the corresponding output by index
                  if num_outdirs == 1 and num_out > 1:
                      for output in t.get_outputs():
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdirs[0], {}, False, {}, None, install_mode,
                                                optional=not t.build_by_default)
                          d.targets.append(i)
                  else:
                      for output, outdir in zip(t.get_outputs(), outdirs):
                          # User requested that we not install this output
                          if outdir is False:
                              continue
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode,
                                                optional=not t.build_by_default)
                          d.targets.append(i)
  
      def generate_custom_install_script(self, d):
          result = []
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for i in self.build.install_scripts:
              exe = i['exe']
              args = i['args']
              fixed_args = []
              for a in args:
                  a = a.replace('@SOURCE_ROOT@', srcdir)
                  a = a.replace('@BUILD_ROOT@', builddir)
                  fixed_args.append(a)
              result.append(build.RunScript(exe, fixed_args))
          d.install_scripts = result
  
      def generate_header_install(self, d):
          incroot = self.environment.get_includedir()
          headers = self.build.get_headers()
  
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for h in headers:
              outdir = h.get_custom_install_dir()
              if outdir is None:
                  outdir = os.path.join(incroot, h.get_install_subdir())
              for f in h.get_sources():
                  if not isinstance(f, File):
                      msg = 'Invalid header type {!r} can\'t be installed'
                      raise MesonException(msg.format(f))
                  abspath = f.absolute_path(srcdir, builddir)
                  i = [abspath, outdir, h.get_custom_install_mode()]
                  d.headers.append(i)
  
      def generate_man_install(self, d):
          manroot = self.environment.get_mandir()
          man = self.build.get_man()
          for m in man:
              for f in m.get_sources():
                  num = f.split('.')[-1]
                  subdir = m.get_custom_install_dir()
                  if subdir is None:
                      subdir = os.path.join(manroot, 'man' + num)
                  srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
                  dstabs = os.path.join(subdir, os.path.basename(f.fname))
                  i = [srcabs, dstabs, m.get_custom_install_mode()]
                  d.man.append(i)
  
      def generate_data_install(self, d):
          data = self.build.get_data()
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for de in data:
              assert(isinstance(de, build.Data))
              subdir = de.install_dir
              if not subdir:
                  subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name)
              for src_file, dst_name in zip(de.sources, de.rename):
                  assert(isinstance(src_file, mesonlib.File))
                  dst_abs = os.path.join(subdir, dst_name)
                  i = [src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode]
                  d.data.append(i)
  
      def generate_subdir_install(self, d):
          for sd in self.build.get_install_subdirs():
              src_dir = os.path.join(self.environment.get_source_dir(),
                                     sd.source_subdir,
                                     sd.installable_subdir).rstrip('/')
              dst_dir = os.path.join(self.environment.get_prefix(),
                                     sd.install_dir)
              if not sd.strip_directory:
                  dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
              d.install_subdirs.append([src_dir, dst_dir, sd.install_mode,
                                        sd.exclude])
  
      def get_introspection_data(self, target_id, target):
          '''
          Returns a list of source dicts with the following format for a given target:
          [
              {
                  "language": "<LANG>",
                  "compiler": ["result", "of", "comp.get_exelist()"],
                  "parameters": ["list", "of", "compiler", "parameters],
                  "sources": ["list", "of", "all", "<LANG>", "source", "files"],
                  "generated_sources": ["list", "of", "generated", "source", "files"]
              }
          ]
  
          This is a limited fallback / reference implementation. The backend should override this method.
          '''
          if isinstance(target, (build.CustomTarget, build.BuildTarget)):
              source_list_raw = target.sources + target.extra_files
              source_list = []
              for j in source_list_raw:
                  if isinstance(j, mesonlib.File):
                      source_list += [j.absolute_path(self.source_dir, self.build_dir)]
                  elif isinstance(j, str):
                      source_list += [os.path.join(self.source_dir, j)]
              source_list = list(map(lambda x: os.path.normpath(x), source_list))
  
              compiler = []
              if isinstance(target, build.CustomTarget):
                  tmp_compiler = target.command
                  if not isinstance(compiler, list):
                      tmp_compiler = [compiler]
                  for j in tmp_compiler:
                      if isinstance(j, mesonlib.File):
                          compiler += [j.absolute_path(self.source_dir, self.build_dir)]
                      elif isinstance(j, str):
                          compiler += [j]
                      elif isinstance(j, (build.BuildTarget, build.CustomTarget)):
                          compiler += j.get_outputs()
                      else:
                          raise RuntimeError('Type "{}" is not supported in get_introspection_data. This is a bug'.format(type(j).__name__))
  
              return [{
                  'language': 'unknown',
                  'compiler': compiler,
                  'parameters': [],
                  'sources': source_list,
                  'generated_sources': []
              }]
  
          return []
  
files: mesonbuild/backend/backends.py
files: # Copyright 2012-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from collections import OrderedDict
  from functools import lru_cache
  import json
  import os
  import pickle
  import re
  import shlex
  import subprocess
  import textwrap
  import typing as T
  
  from .. import build
  from .. import dependencies
  from .. import mesonlib
  from .. import mlog
  from ..compilers import CompilerArgs, VisualStudioLikeCompiler
  from ..interpreter import Interpreter
  from ..mesonlib import (
      File, MachineChoice, MesonException, OrderedSet, OptionOverrideProxy,
      classify_unity_sources, unholder
  )
  
  
  class CleanTrees:
      '''
      Directories outputted by custom targets that have to be manually cleaned
      because on Linux `ninja clean` only deletes empty directories.
      '''
      def __init__(self, build_dir, trees):
          self.build_dir = build_dir
          self.trees = trees
  
  class InstallData:
      def __init__(self, source_dir, build_dir, prefix, strip_bin,
                   install_umask, mesonintrospect):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.prefix = prefix
          self.strip_bin = strip_bin
          self.install_umask = install_umask
          self.targets = []
          self.headers = []
          self.man = []
          self.data = []
          self.po_package_name = ''
          self.po = []
          self.install_scripts = []
          self.install_subdirs = []
          self.mesonintrospect = mesonintrospect
  
  class TargetInstallData:
      def __init__(self, fname, outdir, aliases, strip, install_name_mappings, install_rpath, install_mode, optional=False):
          self.fname = fname
          self.outdir = outdir
          self.aliases = aliases
          self.strip = strip
          self.install_name_mappings = install_name_mappings
          self.install_rpath = install_rpath
          self.install_mode = install_mode
          self.optional = optional
  
  class ExecutableSerialisation:
      def __init__(self, cmd_args, env=None, exe_wrapper=None,
                   workdir=None, extra_paths=None, capture=None):
          self.cmd_args = cmd_args
          self.env = env or {}
          if exe_wrapper is not None:
              assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
          self.exe_runner = exe_wrapper
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.capture = capture
  
  class TestSerialisation:
      def __init__(self, name: str, project: str, suite: str, fname: T.List[str],
                   is_cross_built: bool, exe_wrapper: T.Optional[build.Executable],
                   needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str],
                   env: build.EnvironmentVariables, should_fail: bool,
                   timeout: T.Optional[int], workdir: T.Optional[str],
                   extra_paths: T.List[str], protocol: str, priority: int):
          self.name = name
          self.project_name = project
          self.suite = suite
          self.fname = fname
          self.is_cross_built = is_cross_built
          if exe_wrapper is not None:
              assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
          self.exe_runner = exe_wrapper
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.protocol = protocol
          self.priority = priority
          self.needs_exe_wrapper = needs_exe_wrapper
  
  def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional[Interpreter] = None) -> T.Optional['Backend']:
      if backend == 'ninja':
          from . import ninjabackend
          return ninjabackend.NinjaBackend(build, interpreter)
      elif backend == 'vs':
          from . import vs2010backend
          return vs2010backend.autodetect_vs_version(build, interpreter)
      elif backend == 'vs2010':
          from . import vs2010backend
          return vs2010backend.Vs2010Backend(build, interpreter)
      elif backend == 'vs2015':
          from . import vs2015backend
          return vs2015backend.Vs2015Backend(build, interpreter)
      elif backend == 'vs2017':
          from . import vs2017backend
          return vs2017backend.Vs2017Backend(build, interpreter)
      elif backend == 'vs2019':
          from . import vs2019backend
          return vs2019backend.Vs2019Backend(build, interpreter)
      elif backend == 'xcode':
          from . import xcodebackend
          return xcodebackend.XCodeBackend(build, interpreter)
      return None
  
  # This class contains the basic functionality that is needed by all backends.
  # Feel free to move stuff in and out of it as you see fit.
  class Backend:
      def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
          # Make it possible to construct a dummy backend
          # This is used for introspection without a build directory
          if build is None:
              self.environment = None
              return
          self.build = build
          self.interpreter = interpreter
          self.environment = build.environment
          self.processed_targets = {}
          self.build_dir = self.environment.get_build_dir()
          self.source_dir = self.environment.get_source_dir()
          self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(),
                                               self.environment.get_build_dir())
  
      def get_target_filename(self, t):
          if isinstance(t, build.CustomTarget):
              if len(t.get_outputs()) != 1:
                  mlog.warning('custom_target {!r} has more than one output! '
                               'Using the first one.'.format(t.name))
              filename = t.get_outputs()[0]
          elif isinstance(t, build.CustomTargetIndex):
              filename = t.get_outputs()[0]
          else:
              assert(isinstance(t, build.BuildTarget))
              filename = t.get_filename()
          return os.path.join(self.get_target_dir(t), filename)
  
      def get_target_filename_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))
  
      def get_base_options_for_target(self, target):
          return OptionOverrideProxy(target.option_overrides_base,
                                     self.environment.coredata.builtins,
                                     self.environment.coredata.base_options)
  
      def get_compiler_options_for_target(self, target):
          comp_reg = self.environment.coredata.compiler_options[target.for_machine]
          comp_override = target.option_overrides_compiler
          return {
              lang: OptionOverrideProxy(comp_override[lang], comp_reg[lang])
              for lang in set(comp_reg.keys()) | set(comp_override.keys())
          }
  
      def get_option_for_target(self, option_name, target):
          if option_name in target.option_overrides_base:
              override = target.option_overrides_base[option_name]
              return self.environment.coredata.validate_option_value(option_name, override)
          return self.environment.coredata.get_builtin_option(option_name, target.subproject)
  
      def get_target_filename_for_linking(self, target):
          # On some platforms (msvc for instance), the file that is used for
          # dynamic linking is not the same as the dynamic library itself. This
          # file is called an import library, and we want to link against that.
          # On all other platforms, we link to the library directly.
          if isinstance(target, build.SharedLibrary):
              link_lib = target.get_import_filename() or target.get_filename()
              return os.path.join(self.get_target_dir(target), link_lib)
          elif isinstance(target, build.StaticLibrary):
              return os.path.join(self.get_target_dir(target), target.get_filename())
          elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)):
              if not target.is_linkable_target():
                  raise MesonException('Tried to link against custom target "%s", which is not linkable.' % target.name)
              return os.path.join(self.get_target_dir(target), target.get_filename())
          elif isinstance(target, build.Executable):
              if target.import_filename:
                  return os.path.join(self.get_target_dir(target), target.get_import_filename())
              else:
                  return None
          raise AssertionError('BUG: Tried to link to {!r} which is not linkable'.format(target))
  
      @lru_cache(maxsize=None)
      def get_target_dir(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              dirname = target.get_subdir()
          else:
              dirname = 'meson-out'
          return dirname
  
      def get_target_dir_relative_to(self, t, o):
          '''Get a target dir relative to another target's directory'''
          target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t))
          othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o))
          return os.path.relpath(target_dir, othert_dir)
  
      def get_target_source_dir(self, target):
          # if target dir is empty, avoid extraneous trailing / from os.path.join()
          target_dir = self.get_target_dir(target)
          if target_dir:
              return os.path.join(self.build_to_src, target_dir)
          return self.build_to_src
  
      def get_target_private_dir(self, target):
          return os.path.join(self.get_target_dir(target), target.get_id())
  
      def get_target_private_dir_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target))
  
      @lru_cache(maxsize=None)
      def get_target_generated_dir(self, target, gensrc, src):
          """
          Takes a BuildTarget, a generator source (CustomTarget or GeneratedList),
          and a generated source filename.
          Returns the full path of the generated source relative to the build root
          """
          # CustomTarget generators output to the build dir of the CustomTarget
          if isinstance(gensrc, (build.CustomTarget, build.CustomTargetIndex)):
              return os.path.join(self.get_target_dir(gensrc), src)
          # GeneratedList generators output to the private build directory of the
          # target that the GeneratedList is used in
          return os.path.join(self.get_target_private_dir(target), src)
  
      def get_unity_source_file(self, target, suffix, number):
          # There is a potential conflict here, but it is unlikely that
          # anyone both enables unity builds and has a file called foo-unity.cpp.
          osrc = '{}-unity{}.{}'.format(target.name, number, suffix)
          return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc)
  
      def generate_unity_files(self, target, unity_src):
          abs_files = []
          result = []
          compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
          unity_size = self.get_option_for_target('unity_size', target)
  
          def init_language_file(suffix, unity_file_number):
              unity_src = self.get_unity_source_file(target, suffix, unity_file_number)
              outfileabs = unity_src.absolute_path(self.environment.get_source_dir(),
                                                   self.environment.get_build_dir())
              outfileabs_tmp = outfileabs + '.tmp'
              abs_files.append(outfileabs)
              outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp)
              if not os.path.exists(outfileabs_tmp_dir):
                  os.makedirs(outfileabs_tmp_dir)
              result.append(unity_src)
              return open(outfileabs_tmp, 'w')
  
          # For each language, generate unity source files and return the list
          for comp, srcs in compsrcs.items():
              files_in_current = unity_size + 1
              unity_file_number = 0
              ofile = None
              for src in srcs:
                  if files_in_current >= unity_size:
                      if ofile:
                          ofile.close()
                      ofile = init_language_file(comp.get_default_suffix(), unity_file_number)
                      unity_file_number += 1
                      files_in_current = 0
                  ofile.write('#include<%s>\n' % src)
                  files_in_current += 1
              if ofile:
                  ofile.close()
  
          [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files]
          return result
  
      def relpath(self, todir, fromdir):
          return os.path.relpath(os.path.join('dummyprefixdir', todir),
                                 os.path.join('dummyprefixdir', fromdir))
  
      def flatten_object_list(self, target, proj_dir_to_build_root=''):
          obj_list = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root)
          return list(dict.fromkeys(obj_list))
  
      def _flatten_object_list(self, target, objects, proj_dir_to_build_root):
          obj_list = []
          for obj in objects:
              if isinstance(obj, str):
                  o = os.path.join(proj_dir_to_build_root,
                                   self.build_to_src, target.get_subdir(), obj)
                  obj_list.append(o)
              elif isinstance(obj, mesonlib.File):
                  obj_list.append(obj.rel_to_builddir(self.build_to_src))
              elif isinstance(obj, build.ExtractedObjects):
                  if obj.recursive:
                      obj_list += self._flatten_object_list(obj.target, obj.objlist, proj_dir_to_build_root)
                  obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root)
              else:
                  raise MesonException('Unknown data type in object list.')
          return obj_list
  
      def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None,
                               for_machine=MachineChoice.BUILD,
                               extra_bdeps=None, capture=None, force_serialize=False):
          '''
          Serialize an executable for running with a generator or a custom target
          '''
          import hashlib
          machine = self.environment.machines[for_machine]
          if machine.is_windows() or machine.is_cygwin():
              extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or [])
          else:
              extra_paths = []
  
          if isinstance(exe, dependencies.ExternalProgram):
              exe_cmd = exe.get_command()
              exe_for_machine = exe.for_machine
          elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
              exe_cmd = [self.get_target_filename_abs(exe)]
              exe_for_machine = exe.for_machine
          else:
              exe_cmd = [exe]
              exe_for_machine = MachineChoice.BUILD
  
          is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine)
          if is_cross_built and self.environment.need_exe_wrapper():
              exe_wrapper = self.environment.get_exe_wrapper()
              if not exe_wrapper.found():
                  msg = 'The exe_wrapper {!r} defined in the cross file is ' \
                        'needed by target {!r}, but was not found. Please ' \
                        'check the command and/or add it to PATH.'
                  raise MesonException(msg.format(exe_wrapper.name, tname))
          else:
              if exe_cmd[0].endswith('.jar'):
                  exe_cmd = ['java', '-jar'] + exe_cmd
              elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin()):
                  exe_cmd = ['mono'] + exe_cmd
              exe_wrapper = None
  
          force_serialize = force_serialize or extra_paths or workdir or \
              exe_wrapper or any('\n' in c for c in cmd_args)
          if not force_serialize:
              if not capture:
                  return None
              return (self.environment.get_build_command() +
                      ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args)
  
          workdir = workdir or self.environment.get_build_dir()
          env = {}
          if isinstance(exe, (dependencies.ExternalProgram,
                              build.BuildTarget, build.CustomTarget)):
              basename = exe.name
          else:
              basename = os.path.basename(exe)
  
          # Can't just use exe.name here; it will likely be run more than once
          # Take a digest of the cmd args, env, workdir, and capture. This avoids
          # collisions and also makes the name deterministic over regenerations
          # which avoids a rebuild by Ninja because the cmdline stays the same.
          data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture),
                       encoding='utf-8')
          digest = hashlib.sha1(data).hexdigest()
          scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest)
          exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
          with open(exe_data, 'wb') as f:
              es = ExecutableSerialisation(exe_cmd + cmd_args, env,
                                           exe_wrapper, workdir,
                                           extra_paths, capture)
              pickle.dump(es, f)
          return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data]
  
      def serialize_tests(self):
          test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat')
          with open(test_data, 'wb') as datafile:
              self.write_test_file(datafile)
          benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat')
          with open(benchmark_data, 'wb') as datafile:
              self.write_benchmark_file(datafile)
          return test_data, benchmark_data
  
      def determine_linker_and_stdlib_args(self, target):
          '''
          If we're building a static library, there is only one static linker.
          Otherwise, we query the target for the dynamic linker.
          '''
          if isinstance(target, build.StaticLibrary):
              return self.build.static_linker[target.for_machine], []
          l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs()
          return l, stdlib_args
  
      @staticmethod
      def _libdir_is_system(libdir, compilers, env):
          libdir = os.path.normpath(libdir)
          for cc in compilers.values():
              if libdir in cc.get_library_dirs(env):
                  return True
          return False
  
      def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True):
          paths = []
          for dep in target.external_deps:
              if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)):
                  continue
              la = dep.link_args
              if len(la) != 1 or not os.path.isabs(la[0]):
                  continue
              # The only link argument is an absolute path to a library file.
              libpath = la[0]
              libdir = os.path.dirname(libpath)
              if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
                  # No point in adding system paths.
                  continue
              # Windows doesn't support rpaths, but we use this function to
              # emulate rpaths by setting PATH, so also accept DLLs here
              if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']:
                  continue
              if libdir.startswith(self.environment.get_source_dir()):
                  rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:]
                  assert not os.path.isabs(rel_to_src), 'rel_to_src: {} is absolute'.format(rel_to_src)
                  paths.append(os.path.join(self.build_to_src, rel_to_src))
              else:
                  paths.append(libdir)
          return paths
  
      def determine_rpath_dirs(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              result = target.get_link_dep_subdirs()
          else:
              result = OrderedSet()
              result.add('meson-out')
          result.update(self.rpaths_for_bundled_shared_libraries(target))
          return tuple(result)
  
      def object_filename_from_source(self, target, source):
          assert isinstance(source, mesonlib.File)
          build_dir = self.environment.get_build_dir()
          rel_src = source.rel_to_builddir(self.build_to_src)
  
          # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o
          if rel_src.endswith(('.vala', '.gs')):
              # See description in generate_vala_compile for this logic.
              if source.is_built:
                  if os.path.isabs(rel_src):
                      rel_src = rel_src[len(build_dir) + 1:]
                  rel_src = os.path.relpath(rel_src, self.get_target_private_dir(target))
              else:
                  rel_src = os.path.basename(rel_src)
              # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
              source = 'meson-generated_' + rel_src[:-5] + '.c'
          elif source.is_built:
              if os.path.isabs(rel_src):
                  rel_src = rel_src[len(build_dir) + 1:]
              targetdir = self.get_target_private_dir(target)
              # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
              source = 'meson-generated_' + os.path.relpath(rel_src, targetdir)
          else:
              if os.path.isabs(rel_src):
                  # Use the absolute path directly to avoid file name conflicts
                  source = rel_src
              else:
                  source = os.path.relpath(os.path.join(build_dir, rel_src),
                                           os.path.join(self.environment.get_source_dir(), target.get_subdir()))
          machine = self.environment.machines[target.for_machine]
          return source.replace('/', '_').replace('\\', '_') + '.' + machine.get_object_suffix()
  
      def determine_ext_objs(self, extobj, proj_dir_to_build_root):
          result = []
  
          # Merge sources and generated sources
          sources = list(extobj.srclist)
          for gensrc in extobj.genlist:
              for s in gensrc.get_outputs():
                  path = self.get_target_generated_dir(extobj.target, gensrc, s)
                  dirpart, fnamepart = os.path.split(path)
                  sources.append(File(True, dirpart, fnamepart))
  
          # Filter out headers and all non-source files
          filtered_sources = []
          for s in sources:
              if self.environment.is_source(s) and not self.environment.is_header(s):
                  filtered_sources.append(s)
              elif self.environment.is_object(s):
                  result.append(s.relative_name())
          sources = filtered_sources
  
          # extobj could contain only objects and no sources
          if not sources:
              return result
  
          targetdir = self.get_target_private_dir(extobj.target)
  
          # With unity builds, sources don't map directly to objects,
          # we only support extracting all the objects in this mode,
          # so just return all object files.
          if self.is_unity(extobj.target):
              compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources)
              sources = []
              unity_size = self.get_option_for_target('unity_size', extobj.target)
              for comp, srcs in compsrcs.items():
                  for i in range(len(srcs) // unity_size + 1):
                      osrc = self.get_unity_source_file(extobj.target,
                                                        comp.get_default_suffix(), i)
                      sources.append(osrc)
  
          for osrc in sources:
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              result.append(objpath)
  
          return result
  
      def get_pch_include_args(self, compiler, target):
          args = []
          pchpath = self.get_target_private_dir(target)
          includeargs = compiler.get_include_args(pchpath, False)
          p = target.get_pch(compiler.get_language())
          if p:
              args += compiler.get_pch_use_args(pchpath, p[0])
          return includeargs + args
  
      def create_msvc_pch_implementation(self, target, lang, pch_header):
          # We have to include the language in the file name, otherwise
          # pch.c and pch.cpp will both end up as pch.obj in VS backends.
          impl_name = 'meson_pch-%s.%s' % (lang, lang)
          pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name)
          # Make sure to prepend the build dir, since the working directory is
          # not defined. Otherwise, we might create the file in the wrong path.
          pch_file = os.path.join(self.build_dir, pch_rel_to_build)
          os.makedirs(os.path.dirname(pch_file), exist_ok=True)
  
          content = '#include "%s"' % os.path.basename(pch_header)
          pch_file_tmp = pch_file + '.tmp'
          with open(pch_file_tmp, 'w') as f:
              f.write(content)
          mesonlib.replace_if_different(pch_file, pch_file_tmp)
          return pch_rel_to_build
  
      @staticmethod
      def escape_extra_args(compiler, args):
          # No extra escaping/quoting needed when not running on Windows
          if not mesonlib.is_windows():
              return args
          extra_args = []
          # Compiler-specific escaping is needed for -D args but not for any others
          if isinstance(compiler, VisualStudioLikeCompiler):
              # MSVC needs escaping when a -D argument ends in \ or \"
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      # Without extra escaping for these two, the next character
                      # gets eaten
                      if arg.endswith('\\'):
                          arg += '\\'
                      elif arg.endswith('\\"'):
                          arg = arg[:-2] + '\\\\"'
                  extra_args.append(arg)
          else:
              # MinGW GCC needs all backslashes in defines to be doubly-escaped
              # FIXME: Not sure about Cygwin or Clang
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      arg = arg.replace('\\', '\\\\')
                  extra_args.append(arg)
          return extra_args
  
      def generate_basic_compiler_args(self, target, compiler, no_warn_args=False):
          # Create an empty commands list, and start adding arguments from
          # various sources in the order in which they must override each other
          # starting from hard-coded defaults followed by build options and so on.
          commands = CompilerArgs(compiler)
  
          copt_proxy = self.get_compiler_options_for_target(target)[compiler.language]
          # First, the trivial ones that are impossible to override.
          #
          # Add -nostdinc/-nostdinc++ if needed; can't be overridden
          commands += self.get_cross_stdlib_args(target, compiler)
          # Add things like /NOLOGO or -pipe; usually can't be overridden
          commands += compiler.get_always_args()
          # Only add warning-flags by default if the buildtype enables it, and if
          # we weren't explicitly asked to not emit warnings (for Vala, f.ex)
          if no_warn_args:
              commands += compiler.get_no_warn_args()
          elif self.get_option_for_target('buildtype', target) != 'plain':
              commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target))
          # Add -Werror if werror=true is set in the build options set on the
          # command-line or default_options inside project(). This only sets the
          # action to be done for warnings if/when they are emitted, so it's ok
          # to set it after get_no_warn_args() or get_warn_args().
          if self.get_option_for_target('werror', target):
              commands += compiler.get_werror_args()
          # Add compile args for c_* or cpp_* build options set on the
          # command-line or default_options inside project().
          commands += compiler.get_option_compile_args(copt_proxy)
          # Add buildtype args: optimization level, debugging, etc.
          commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target))
          commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target))
          commands += compiler.get_debug_args(self.get_option_for_target('debug', target))
          # Add compile args added using add_project_arguments()
          commands += self.build.get_project_args(compiler, target.subproject, target.for_machine)
          # Add compile args added using add_global_arguments()
          # These override per-project arguments
          commands += self.build.get_global_args(compiler, target.for_machine)
          # Compile args added from the env: CFLAGS/CXXFLAGS, etc, or the cross
          # file. We want these to override all the defaults, but not the
          # per-target compile args.
          commands += self.environment.coredata.get_external_args(target.for_machine, compiler.get_language())
          # Always set -fPIC for shared libraries
          if isinstance(target, build.SharedLibrary):
              commands += compiler.get_pic_args()
          # Set -fPIC for static libraries by default unless explicitly disabled
          if isinstance(target, build.StaticLibrary) and target.pic:
              commands += compiler.get_pic_args()
          if isinstance(target, build.Executable) and target.pie:
              commands += compiler.get_pie_args()
          # Add compile args needed to find external dependencies. Link args are
          # added while generating the link command.
          # NOTE: We must preserve the order in which external deps are
          # specified, so we reverse the list before iterating over it.
          for dep in reversed(target.get_external_deps()):
              if not dep.found():
                  continue
  
              if compiler.language == 'vala':
                  if isinstance(dep, dependencies.PkgConfigDependency):
                      if dep.name == 'glib-2.0' and dep.version_reqs is not None:
                          for req in dep.version_reqs:
                              if req.startswith(('>=', '==')):
                                  commands += ['--target-glib', req[2:]]
                                  break
                      commands += ['--pkg', dep.name]
                  elif isinstance(dep, dependencies.ExternalLibrary):
                      commands += dep.get_link_args('vala')
              else:
                  commands += compiler.get_dependency_compile_args(dep)
              # Qt needs -fPIC for executables
              # XXX: We should move to -fPIC for all executables
              if isinstance(target, build.Executable):
                  commands += dep.get_exe_args(compiler)
              # For 'automagic' deps: Boost and GTest. Also dependency('threads').
              # pkg-config puts the thread flags itself via `Cflags:`
          # Fortran requires extra include directives.
          if compiler.language == 'fortran':
              for lt in target.link_targets:
                  priv_dir = self.get_target_private_dir(lt)
                  commands += compiler.get_include_args(priv_dir, False)
          return commands
  
      def build_target_link_arguments(self, compiler, deps):
          args = []
          for d in deps:
              if not (d.is_linkable_target()):
                  raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename())
              arg = self.get_target_filename_for_linking(d)
              if not arg:
                  continue
              if compiler.get_language() == 'd':
                  arg = '-Wl,' + arg
              else:
                  arg = compiler.get_linker_lib_prefix() + arg
              args.append(arg)
          return args
  
      def get_mingw_extra_paths(self, target):
          paths = OrderedSet()
          # The cross bindir
          root = self.environment.properties[target.for_machine].get_root()
          if root:
              paths.add(os.path.join(root, 'bin'))
          # The toolchain bindir
          sys_root = self.environment.properties[target.for_machine].get_sys_root()
          if sys_root:
              paths.add(os.path.join(sys_root, 'bin'))
          # Get program and library dirs from all target compilers
          if isinstance(target, build.BuildTarget):
              for cc in target.compilers.values():
                  paths.update(cc.get_program_dirs(self.environment))
                  paths.update(cc.get_library_dirs(self.environment))
          return list(paths)
  
      def determine_windows_extra_paths(self, target: T.Union[build.BuildTarget, str], extra_bdeps):
          '''On Windows there is no such thing as an rpath.
          We must determine all locations of DLLs that this exe
          links to and return them so they can be used in unit
          tests.'''
          result = set()
          prospectives = set()
          if isinstance(target, build.BuildTarget):
              prospectives.update(target.get_transitive_link_deps())
              # External deps
              for deppath in self.rpaths_for_bundled_shared_libraries(target, exclude_system=False):
                  result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath)))
          for bdep in extra_bdeps:
              prospectives.update(bdep.get_transitive_link_deps())
          # Internal deps
          for ld in prospectives:
              if ld == '' or ld == '.':
                  continue
              dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld))
              result.add(dirseg)
          if (isinstance(target, build.BuildTarget) and
                  not self.environment.machines.matches_build_machine(target.for_machine)):
              result.update(self.get_mingw_extra_paths(target))
          return list(result)
  
      def write_benchmark_file(self, datafile):
          self.write_test_serialisation(self.build.get_benchmarks(), datafile)
  
      def write_test_file(self, datafile):
          self.write_test_serialisation(self.build.get_tests(), datafile)
  
      def create_test_serialisation(self, tests):
          arr = []
          for t in sorted(tests, key=lambda tst: -1 * tst.priority):
              exe = t.get_exe()
              if isinstance(exe, dependencies.ExternalProgram):
                  cmd = exe.get_command()
              else:
                  cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
              if isinstance(exe, (build.BuildTarget, dependencies.ExternalProgram)):
                  test_for_machine = exe.for_machine
              else:
                  # E.g. an external verifier or simulator program run on a generated executable.
                  # Can always be run without a wrapper.
                  test_for_machine = MachineChoice.BUILD
              is_cross = not self.environment.machines.matches_build_machine(test_for_machine)
              if is_cross and self.environment.need_exe_wrapper():
                  exe_wrapper = self.environment.get_exe_wrapper()
              else:
                  exe_wrapper = None
              machine = self.environment.machines[exe.for_machine]
              if machine.is_windows() or machine.is_cygwin():
                  extra_bdeps = []
                  if isinstance(exe, build.CustomTarget):
                      extra_bdeps = exe.get_transitive_build_target_deps()
                  extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps)
              else:
                  extra_paths = []
              cmd_args = []
              for a in unholder(t.cmd_args):
                  if isinstance(a, build.BuildTarget):
                      extra_paths += self.determine_windows_extra_paths(a, [])
                  if isinstance(a, mesonlib.File):
                      a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
                      cmd_args.append(a)
                  elif isinstance(a, str):
                      cmd_args.append(a)
                  elif isinstance(a, build.Target):
                      cmd_args.append(self.construct_target_rel_path(a, t.workdir))
                  else:
                      raise MesonException('Bad object in test command.')
              ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross,
                                     exe_wrapper, self.environment.need_exe_wrapper(),
                                     t.is_parallel, cmd_args, t.env,
                                     t.should_fail, t.timeout, t.workdir,
                                     extra_paths, t.protocol, t.priority)
              arr.append(ts)
          return arr
  
      def write_test_serialisation(self, tests, datafile):
          pickle.dump(self.create_test_serialisation(tests), datafile)
  
      def construct_target_rel_path(self, a, workdir):
          if workdir is None:
              return self.get_target_filename(a)
          assert(os.path.isabs(workdir))
          abs_path = self.get_target_filename_abs(a)
          return os.path.relpath(abs_path, workdir)
  
      def generate_depmf_install(self, d):
          if self.build.dep_manifest_name is None:
              return
          ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
          ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
          mfobj = {'type': 'dependency manifest', 'version': '1.0', 'projects': self.build.dep_manifest}
          with open(ifilename, 'w') as f:
              f.write(json.dumps(mfobj))
          # Copy file from, to, and with mode unchanged
          d.data.append([ifilename, ofilename, None])
  
      def get_regen_filelist(self):
          '''List of all files whose alteration means that the build
          definition needs to be regenerated.'''
          deps = [os.path.join(self.build_to_src, df)
                  for df in self.interpreter.get_build_def_files()]
          if self.environment.is_cross_build():
              deps.extend(self.environment.coredata.cross_files)
          deps.extend(self.environment.coredata.config_files)
          deps.append('meson-private/coredata.dat')
          self.check_clock_skew(deps)
          return deps
  
      def check_clock_skew(self, file_list):
          # If a file that leads to reconfiguration has a time
          # stamp in the future, it will trigger an eternal reconfigure
          # loop.
          import time
          now = time.time()
          for f in file_list:
              absf = os.path.join(self.environment.get_build_dir(), f)
              ftime = os.path.getmtime(absf)
              delta = ftime - now
              # On Windows disk time stamps sometimes point
              # to the future by a minuscule amount, less than
              # 0.001 seconds. I don't know why.
              if delta > 0.001:
                  raise MesonException('Clock skew detected. File {} has a time stamp {:.4f}s in the future.'.format(absf, delta))
  
      def build_target_to_cmd_array(self, bt):
          if isinstance(bt, build.BuildTarget):
              if isinstance(bt, build.Executable) and bt.for_machine is not MachineChoice.BUILD:
                  if (self.environment.is_cross_build() and
                          self.environment.exe_wrapper is None and
                          self.environment.need_exe_wrapper()):
                      s = textwrap.dedent('''
                          Cannot use target {} as a generator because it is built for the
                          host machine and no exe wrapper is defined or needs_exe_wrapper is
                          true. You might want to set `native: true` instead to build it for
                          the build machine.'''.format(bt.name))
                      raise MesonException(s)
              arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))]
          else:
              arr = bt.get_command()
          return arr
  
      def replace_extra_args(self, args, genlist):
          final_args = []
          for a in args:
              if a == '@EXTRA_ARGS@':
                  final_args += genlist.get_extra_args()
              else:
                  final_args.append(a)
          return final_args
  
      def replace_outputs(self, args, private_dir, output_list):
          newargs = []
          regex = re.compile(r'@OUTPUT(\d+)@')
          for arg in args:
              m = regex.search(arg)
              while m is not None:
                  index = int(m.group(1))
                  src = '@OUTPUT%d@' % index
                  arg = arg.replace(src, os.path.join(private_dir, output_list[index]))
                  m = regex.search(arg)
              newargs.append(arg)
          return newargs
  
      def get_build_by_default_targets(self):
          result = OrderedDict()
          # Get all build and custom targets that must be built by default
          for name, t in self.build.get_targets().items():
              if t.build_by_default:
                  result[name] = t
          # Get all targets used as test executables and arguments. These must
          # also be built by default. XXX: Sometime in the future these should be
          # built only before running tests.
          for t in self.build.get_tests():
              exe = unholder(t.exe)
              if isinstance(exe, (build.CustomTarget, build.BuildTarget)):
                  result[exe.get_id()] = exe
              for arg in unholder(t.cmd_args):
                  if not isinstance(arg, (build.CustomTarget, build.BuildTarget)):
                      continue
                  result[arg.get_id()] = arg
              for dep in t.depends:
                  assert isinstance(dep, (build.CustomTarget, build.BuildTarget))
                  result[dep.get_id()] = dep
          return result
  
      @lru_cache(maxsize=None)
      def get_custom_target_provided_by_generated_source(self, generated_source):
          libs = []
          for f in generated_source.get_outputs():
              if self.environment.is_library(f):
                  libs.append(os.path.join(self.get_target_dir(generated_source), f))
          return libs
  
      @lru_cache(maxsize=None)
      def get_custom_target_provided_libraries(self, target):
          libs = []
          for t in target.get_generated_sources():
              if not isinstance(t, build.CustomTarget):
                  continue
              l = self.get_custom_target_provided_by_generated_source(t)
              libs = libs + l
          return libs
  
      def is_unity(self, target):
          optval = self.get_option_for_target('unity', target)
          if optval == 'on' or (optval == 'subprojects' and target.subproject != ''):
              return True
          return False
  
      def get_custom_target_sources(self, target):
          '''
          Custom target sources can be of various object types; strings, File,
          BuildTarget, even other CustomTargets.
          Returns the path to them relative to the build root directory.
          '''
          srcs = []
          for i in unholder(target.get_sources()):
              if isinstance(i, str):
                  fname = [os.path.join(self.build_to_src, target.subdir, i)]
              elif isinstance(i, build.BuildTarget):
                  fname = [self.get_target_filename(i)]
              elif isinstance(i, (build.CustomTarget, build.CustomTargetIndex)):
                  fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
              elif isinstance(i, build.GeneratedList):
                  fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()]
              elif isinstance(i, build.ExtractedObjects):
                  fname = [os.path.join(self.get_target_private_dir(i.target), p) for p in i.get_outputs(self)]
              else:
                  fname = [i.rel_to_builddir(self.build_to_src)]
              if target.absolute_paths:
                  fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
              srcs += fname
          return srcs
  
      def get_custom_target_depend_files(self, target, absolute_paths=False):
          deps = []
          for i in target.depend_files:
              if isinstance(i, mesonlib.File):
                  if absolute_paths:
                      deps.append(i.absolute_path(self.environment.get_source_dir(),
                                                  self.environment.get_build_dir()))
                  else:
                      deps.append(i.rel_to_builddir(self.build_to_src))
              else:
                  if absolute_paths:
                      deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i))
                  else:
                      deps.append(os.path.join(self.build_to_src, target.subdir, i))
          return deps
  
      def eval_custom_target_command(self, target, absolute_outputs=False):
          # We want the outputs to be absolute only when using the VS backend
          # XXX: Maybe allow the vs backend to use relative paths too?
          source_root = self.build_to_src
          build_root = '.'
          outdir = self.get_target_dir(target)
          if absolute_outputs:
              source_root = self.environment.get_source_dir()
              build_root = self.environment.get_build_dir()
              outdir = os.path.join(self.environment.get_build_dir(), outdir)
          outputs = []
          for i in target.get_outputs():
              outputs.append(os.path.join(outdir, i))
          inputs = self.get_custom_target_sources(target)
          # Evaluate the command list
          cmd = []
          for i in target.command:
              if isinstance(i, build.BuildTarget):
                  cmd += self.build_target_to_cmd_array(i)
                  continue
              elif isinstance(i, build.CustomTarget):
                  # GIR scanner will attempt to execute this binary but
                  # it assumes that it is in path, so always give it a full path.
                  tmp = i.get_outputs()[0]
                  i = os.path.join(self.get_target_dir(i), tmp)
              elif isinstance(i, mesonlib.File):
                  i = i.rel_to_builddir(self.build_to_src)
                  if target.absolute_paths:
                      i = os.path.join(self.environment.get_build_dir(), i)
              # FIXME: str types are blindly added ignoring 'target.absolute_paths'
              # because we can't know if they refer to a file or just a string
              elif not isinstance(i, str):
                  err_msg = 'Argument {0} is of unknown type {1}'
                  raise RuntimeError(err_msg.format(str(i), str(type(i))))
              elif '@SOURCE_ROOT@' in i:
                  i = i.replace('@SOURCE_ROOT@', source_root)
              elif '@BUILD_ROOT@' in i:
                  i = i.replace('@BUILD_ROOT@', build_root)
              elif '@DEPFILE@' in i:
                  if target.depfile is None:
                      msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
                            'keyword argument.'.format(target.name)
                      raise MesonException(msg)
                  dfilename = os.path.join(outdir, target.depfile)
                  i = i.replace('@DEPFILE@', dfilename)
              elif '@PRIVATE_DIR@' in i:
                  if target.absolute_paths:
                      pdir = self.get_target_private_dir_abs(target)
                  else:
                      pdir = self.get_target_private_dir(target)
                  i = i.replace('@PRIVATE_DIR@', pdir)
              elif '@PRIVATE_OUTDIR_' in i:
                  match = re.search(r'@PRIVATE_OUTDIR_(ABS_)?([^/\s*]*)@', i)
                  if not match:
                      msg = 'Custom target {!r} has an invalid argument {!r}' \
                            ''.format(target.name, i)
                      raise MesonException(msg)
                  source = match.group(0)
                  if match.group(1) is None and not target.absolute_paths:
                      lead_dir = ''
                  else:
                      lead_dir = self.environment.get_build_dir()
                  i = i.replace(source, os.path.join(lead_dir, outdir))
              cmd.append(i)
          # Substitute the rest of the template strings
          values = mesonlib.get_filenames_templates_dict(inputs, outputs)
          cmd = mesonlib.substitute_values(cmd, values)
          # This should not be necessary but removing it breaks
          # building GStreamer on Windows. The underlying issue
          # is problems with quoting backslashes on Windows
          # which is the seventh circle of hell. The downside is
          # that this breaks custom targets whose command lines
          # have backslashes. If you try to fix this be sure to
          # check that it does not break GST.
          #
          # The bug causes file paths such as c:\foo to get escaped
          # into c:\\foo.
          #
          # Unfortunately we have not been able to come up with an
          # isolated test case for this so unless you manage to come up
          # with one, the only way is to test the building with Gst's
          # setup. Note this in your MR or ping us and we will get it
          # fixed.
          #
          # https://github.com/mesonbuild/meson/pull/737
          cmd = [i.replace('\\', '/') for i in cmd]
          return inputs, outputs, cmd
  
      def run_postconf_scripts(self):
          env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
                 'MESON_BUILD_ROOT': self.environment.get_build_dir(),
                 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]),
                 }
          child_env = os.environ.copy()
          child_env.update(env)
  
          for s in self.build.postconf_scripts:
              cmd = s['exe'] + s['args']
              subprocess.check_call(cmd, env=child_env)
  
      def create_install_data(self):
          strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip')
          if strip_bin is None:
              if self.environment.is_cross_build():
                  mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
              else:
                  # TODO go through all candidates, like others
                  strip_bin = [self.environment.default_strip[0]]
          d = InstallData(self.environment.get_source_dir(),
                          self.environment.get_build_dir(),
                          self.environment.get_prefix(),
                          strip_bin,
                          self.environment.coredata.get_builtin_option('install_umask'),
                          self.environment.get_build_command() + ['introspect'])
          self.generate_depmf_install(d)
          self.generate_target_install(d)
          self.generate_header_install(d)
          self.generate_man_install(d)
          self.generate_data_install(d)
          self.generate_custom_install_script(d)
          self.generate_subdir_install(d)
          return d
  
      def create_install_data_files(self):
          install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
          with open(install_data_file, 'wb') as ofile:
              pickle.dump(self.create_install_data(), ofile)
  
      def generate_target_install(self, d):
          for t in self.build.get_targets().values():
              if not t.should_install():
                  continue
              outdirs, custom_install_dir = t.get_install_dir(self.environment)
              # Sanity-check the outputs and install_dirs
              num_outdirs, num_out = len(outdirs), len(t.get_outputs())
              if num_outdirs != 1 and num_outdirs != num_out:
                  m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
                      "Pass 'false' for outputs that should not be installed and 'true' for\n" \
                      'using the default installation directory for an output.'
                  raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs))
              install_mode = t.get_custom_install_mode()
              # Install the target output(s)
              if isinstance(t, build.BuildTarget):
                  # In general, stripping static archives is tricky and full of pitfalls.
                  # Wholesale stripping of static archives with a command such as
                  #
                  #   strip libfoo.a
                  #
                  # is broken, as GNU's strip will remove *every* symbol in a static
                  # archive. One solution to this nonintuitive behaviour would be
                  # to only strip local/debug symbols. Unfortunately, strip arguments
                  # are not specified by POSIX and therefore not portable. GNU's `-g`
                  # option (i.e. remove debug symbols) is equivalent to Apple's `-S`.
                  #
                  # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more
                  #       fine-grained stripping of static archives.
                  should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target('strip', t)
                  # Install primary build output (library/executable/jar, etc)
                  # Done separately because of strip/aliases/rpath
                  if outdirs[0] is not False:
                      mappings = t.get_link_deps_mapping(d.prefix, self.environment)
                      i = TargetInstallData(self.get_target_filename(t), outdirs[0],
                                            t.get_aliases(), should_strip, mappings,
                                            t.install_rpath, install_mode)
                      d.targets.append(i)
  
                      if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)):
                          # On toolchains/platforms that use an import library for
                          # linking (separate from the shared library with all the
                          # code), we need to install that too (dll.a/.lib).
                          if t.get_import_filename():
                              if custom_install_dir:
                                  # If the DLL is installed into a custom directory,
                                  # install the import library into the same place so
                                  # it doesn't go into a surprising place
                                  implib_install_dir = outdirs[0]
                              else:
                                  implib_install_dir = self.environment.get_import_lib_dir()
                              # Install the import library; may not exist for shared modules
                              i = TargetInstallData(self.get_target_filename_for_linking(t),
                                                    implib_install_dir, {}, False, {}, '', install_mode,
                                                    optional=isinstance(t, build.SharedModule))
                              d.targets.append(i)
  
                          if not should_strip and t.get_debug_filename():
                              debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename())
                              i = TargetInstallData(debug_file, outdirs[0],
                                                    {}, False, {}, '',
                                                    install_mode, optional=True)
                              d.targets.append(i)
                  # Install secondary outputs. Only used for Vala right now.
                  if num_outdirs > 1:
                      for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]):
                          # User requested that we not install this output
                          if outdir is False:
                              continue
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode)
                          d.targets.append(i)
              elif isinstance(t, build.CustomTarget):
                  # If only one install_dir is specified, assume that all
                  # outputs will be installed into it. This is for
                  # backwards-compatibility and because it makes sense to
                  # avoid repetition since this is a common use-case.
                  #
                  # To selectively install only some outputs, pass `false` as
                  # the install_dir for the corresponding output by index
                  if num_outdirs == 1 and num_out > 1:
                      for output in t.get_outputs():
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdirs[0], {}, False, {}, None, install_mode,
                                                optional=not t.build_by_default)
                          d.targets.append(i)
                  else:
                      for output, outdir in zip(t.get_outputs(), outdirs):
                          # User requested that we not install this output
                          if outdir is False:
                              continue
                          f = os.path.join(self.get_target_dir(t), output)
                          i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode,
                                                optional=not t.build_by_default)
                          d.targets.append(i)
  
      def generate_custom_install_script(self, d):
          result = []
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for i in self.build.install_scripts:
              exe = i['exe']
              args = i['args']
              fixed_args = []
              for a in args:
                  a = a.replace('@SOURCE_ROOT@', srcdir)
                  a = a.replace('@BUILD_ROOT@', builddir)
                  fixed_args.append(a)
              result.append(build.RunScript(exe, fixed_args))
          d.install_scripts = result
  
      def generate_header_install(self, d):
          incroot = self.environment.get_includedir()
          headers = self.build.get_headers()
  
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for h in headers:
              outdir = h.get_custom_install_dir()
              if outdir is None:
                  outdir = os.path.join(incroot, h.get_install_subdir())
              for f in h.get_sources():
                  if not isinstance(f, File):
                      msg = 'Invalid header type {!r} can\'t be installed'
                      raise MesonException(msg.format(f))
                  abspath = f.absolute_path(srcdir, builddir)
                  i = [abspath, outdir, h.get_custom_install_mode()]
                  d.headers.append(i)
  
      def generate_man_install(self, d):
          manroot = self.environment.get_mandir()
          man = self.build.get_man()
          for m in man:
              for f in m.get_sources():
                  num = f.split('.')[-1]
                  subdir = m.get_custom_install_dir()
                  if subdir is None:
                      subdir = os.path.join(manroot, 'man' + num)
                  srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
                  dstabs = os.path.join(subdir, os.path.basename(f.fname))
                  i = [srcabs, dstabs, m.get_custom_install_mode()]
                  d.man.append(i)
  
      def generate_data_install(self, d):
          data = self.build.get_data()
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
          for de in data:
              assert(isinstance(de, build.Data))
              subdir = de.install_dir
              if not subdir:
                  subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name)
              for src_file, dst_name in zip(de.sources, de.rename):
                  assert(isinstance(src_file, mesonlib.File))
                  dst_abs = os.path.join(subdir, dst_name)
                  i = [src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode]
                  d.data.append(i)
  
      def generate_subdir_install(self, d):
          for sd in self.build.get_install_subdirs():
              src_dir = os.path.join(self.environment.get_source_dir(),
                                     sd.source_subdir,
                                     sd.installable_subdir).rstrip('/')
              dst_dir = os.path.join(self.environment.get_prefix(),
                                     sd.install_dir)
              if not sd.strip_directory:
                  dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
              d.install_subdirs.append([src_dir, dst_dir, sd.install_mode,
                                        sd.exclude])
  
      def get_introspection_data(self, target_id, target):
          '''
          Returns a list of source dicts with the following format for a given target:
          [
              {
                  "language": "<LANG>",
                  "compiler": ["result", "of", "comp.get_exelist()"],
                  "parameters": ["list", "of", "compiler", "parameters],
                  "sources": ["list", "of", "all", "<LANG>", "source", "files"],
                  "generated_sources": ["list", "of", "generated", "source", "files"]
              }
          ]
  
          This is a limited fallback / reference implementation. The backend should override this method.
          '''
          if isinstance(target, (build.CustomTarget, build.BuildTarget)):
              source_list_raw = target.sources + target.extra_files
              source_list = []
              for j in source_list_raw:
                  if isinstance(j, mesonlib.File):
                      source_list += [j.absolute_path(self.source_dir, self.build_dir)]
                  elif isinstance(j, str):
                      source_list += [os.path.join(self.source_dir, j)]
              source_list = list(map(lambda x: os.path.normpath(x), source_list))
  
              compiler = []
              if isinstance(target, build.CustomTarget):
                  tmp_compiler = target.command
                  if not isinstance(compiler, list):
                      tmp_compiler = [compiler]
                  for j in tmp_compiler:
                      if isinstance(j, mesonlib.File):
                          compiler += [j.absolute_path(self.source_dir, self.build_dir)]
                      elif isinstance(j, str):
                          compiler += [j]
                      elif isinstance(j, (build.BuildTarget, build.CustomTarget)):
                          compiler += j.get_outputs()
                      else:
                          raise RuntimeError('Type "{}" is not supported in get_introspection_data. This is a bug'.format(type(j).__name__))
  
              return [{
                  'language': 'unknown',
                  'compiler': compiler,
                  'parameters': [],
                  'sources': source_list,
                  'generated_sources': []
              }]
  
          return []
  
files: mesonbuild/backend/backends.py
files: # Copyright 2012-2019 The Meson development team
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mparser
  from . import environment
  from . import coredata
  from . import dependencies
  from . import mlog
  from . import build
  from . import optinterpreter
  from . import compilers
  from .wrap import wrap, WrapMode
  from . import mesonlib
  from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep, unholder
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  from .depfile import DepFile
  from .interpreterbase import InterpreterBase
  from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
  from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
  from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .interpreterbase import ObjectHolder
  from .modules import ModuleReturnValue
  from .cmake import CMakeInterpreter
  from .backend.backends import TestProtocol
  
  from pathlib import Path, PurePath
  import os
  import shutil
  import uuid
  import re
  import shlex
  import subprocess
  import collections
  import functools
  import typing as T
  
  import importlib
  
  permitted_method_kwargs = {
      'partial_dependency': {'compile_args', 'link_args', 'links', 'includes',
                             'sources'},
  }
  
  def stringifyUserArguments(args):
      if isinstance(args, list):
          return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
      elif isinstance(args, dict):
          return '{%s}' % ', '.join(['%s : %s' % (stringifyUserArguments(k), stringifyUserArguments(v)) for k, v in args.items()])
      elif isinstance(args, int):
          return str(args)
      elif isinstance(args, str):
          return "'%s'" % args
      raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
  
  
  class OverrideProgram(dependencies.ExternalProgram):
      pass
  
  
  class FeatureOptionHolder(InterpreterObject, ObjectHolder):
      def __init__(self, env, name, option):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, option)
          if option.is_auto():
              self.held_object = env.coredata.builtins['auto_features']
          self.name = name
          self.methods.update({'enabled': self.enabled_method,
                               'disabled': self.disabled_method,
                               'auto': self.auto_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def enabled_method(self, args, kwargs):
          return self.held_object.is_enabled()
  
      @noPosargs
      @permittedKwargs({})
      def disabled_method(self, args, kwargs):
          return self.held_object.is_disabled()
  
      @noPosargs
      @permittedKwargs({})
      def auto_method(self, args, kwargs):
          return self.held_object.is_auto()
  
  def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True):
      val = kwargs.get('required', default)
      disabled = False
      required = False
      feature = None
      if isinstance(val, FeatureOptionHolder):
          if not feature_check:
              feature_check = FeatureNew('User option "feature"', '0.47.0')
          feature_check.use(subproject)
          option = val.held_object
          feature = val.name
          if option.is_disabled():
              disabled = True
          elif option.is_enabled():
              required = True
      elif isinstance(val, bool):
          required = val
      else:
          raise InterpreterException('required keyword argument must be boolean or a feature option')
  
      # Keep boolean value in kwargs to simplify other places where this kwarg is
      # checked.
      kwargs['required'] = required
  
      return disabled, required, feature
  
  def extract_search_dirs(kwargs):
      search_dirs = mesonlib.stringlistify(kwargs.get('dirs', []))
      search_dirs = [Path(d).expanduser() for d in search_dirs]
      for d in search_dirs:
          if mesonlib.is_windows() and d.root.startswith('\\'):
              # a Unix-path starting with `/` that is not absolute on Windows.
              # discard without failing for end-user ease of cross-platform directory arrays
              continue
          if not d.is_absolute():
              raise InvalidCode('Search directory {} is not an absolute path.'.format(d))
      return list(map(str, search_dirs))
  
  class TryRunResultHolder(InterpreterObject):
      def __init__(self, res):
          super().__init__()
          self.res = res
          self.methods.update({'returncode': self.returncode_method,
                               'compiled': self.compiled_method,
                               'stdout': self.stdout_method,
                               'stderr': self.stderr_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.res.returncode
  
      @noPosargs
      @permittedKwargs({})
      def compiled_method(self, args, kwargs):
          return self.res.compiled
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.res.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.res.stderr
  
  class RunProcess(InterpreterObject):
  
      def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          self.capture = capture
          pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
          self.returncode = pc.returncode
          self.methods.update({'returncode': self.returncode_method,
                               'stdout': self.stdout_method,
                               'stderr': self.stderr_method,
                               })
  
      def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False):
          command_array = cmd.get_command() + args
          menv = {'MESON_SOURCE_ROOT': source_dir,
                  'MESON_BUILD_ROOT': build_dir,
                  'MESON_SUBDIR': subdir,
                  'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]),
                  }
          if in_builddir:
              cwd = os.path.join(build_dir, subdir)
          else:
              cwd = os.path.join(source_dir, subdir)
          child_env = os.environ.copy()
          child_env.update(menv)
          child_env = env.get_env(child_env)
          stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
              if self.capture:
                  mlog.debug('--- stdout ---')
                  mlog.debug(o)
              else:
                  o = ''
                  mlog.debug('--- stdout disabled ---')
              mlog.debug('--- stderr ---')
              mlog.debug(e)
              mlog.debug('')
  
              if check and p.returncode != 0:
                  raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
  
              return p, o, e
          except FileNotFoundError:
              raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.returncode
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.stderr
  
  class ConfigureFileHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          InterpreterObject.__init__(self)
          obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data)
          ObjectHolder.__init__(self, obj)
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self, initial_values=None):
          MutableInterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.EnvironmentVariables())
          self.methods.update({'set': self.set_method,
                               'append': self.append_method,
                               'prepend': self.prepend_method,
                               })
          if isinstance(initial_values, dict):
              for k, v in initial_values.items():
                  self.set_method([k, v], {})
          elif isinstance(initial_values, list):
              for e in initial_values:
                  if '=' not in e:
                      raise InterpreterException('Env var definition must be of type key=val.')
                  (k, val) = e.split('=', 1)
                  k = k.strip()
                  val = val.strip()
                  if ' ' in k:
                      raise InterpreterException('Env var key must not have spaces in it.')
                  self.set_method([k, val], {})
          elif initial_values:
              raise AssertionError('Unsupported EnvironmentVariablesHolder initial_values')
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.held_object.envvars)
  
      def add_var(self, method, args, kwargs):
          if not isinstance(kwargs.get("separator", ""), str):
              raise InterpreterException("EnvironmentVariablesHolder methods 'separator'"
                                         " argument needs to be a string.")
          if len(args) < 2:
              raise InterpreterException("EnvironmentVariablesHolder methods require at least"
                                         "2 arguments, first is the name of the variable and"
                                         " following one are values")
          # Warn when someone tries to use append() or prepend() on an env var
          # which already has an operation set on it. People seem to think that
          # multiple append/prepend operations stack, but they don't.
          if method != self.held_object.set and self.held_object.has_name(args[0]):
              mlog.warning('Overriding previous value of environment variable {!r} with a new one'
                           .format(args[0]), location=self.current_node)
          self.held_object.add_var(method, args[0], args[1:], kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def set_method(self, args, kwargs):
          self.add_var(self.held_object.set, args, kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def append_method(self, args, kwargs):
          self.add_var(self.held_object.append, args, kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def prepend_method(self, args, kwargs):
          self.add_var(self.held_object.prepend, args, kwargs)
  
  
  class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self, pv, initial_values=None):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData(), pv)
          self.methods.update({'set': self.set_method,
                               'set10': self.set10_method,
                               'set_quoted': self.set_quoted_method,
                               'has': self.has_method,
                               'get': self.get_method,
                               'get_unquoted': self.get_unquoted_method,
                               'merge_from': self.merge_from_method,
                               })
          if isinstance(initial_values, dict):
              for k, v in initial_values.items():
                  self.set_method([k, v], {})
          elif initial_values:
              raise AssertionError('Unsupported ConfigurationDataHolder initial_values')
  
      def is_used(self):
          return self.used
  
      def mark_used(self):
          self.used = True
  
      def validate_args(self, args, kwargs):
          if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
              mlog.deprecation('Passing a list as the single argument to '
                               'configuration_data.set is deprecated. This will '
                               'become a hard error in the future.',
                               location=self.current_node)
              args = args[0]
  
          if len(args) != 2:
              raise InterpreterException("Configuration set requires 2 arguments.")
          if self.used:
              raise InterpreterException("Can not set values on configuration object that has been used.")
          name, val = args
          if not isinstance(val, (int, str)):
              msg = 'Setting a configuration data value to {!r} is invalid, ' \
                    'and will fail at configure_file(). If you are using it ' \
                    'just to store some values, please use a dict instead.'
              mlog.deprecation(msg.format(val), location=self.current_node)
          desc = kwargs.get('description', None)
          if not isinstance(name, str):
              raise InterpreterException("First argument to set must be a string.")
          if desc is not None and not isinstance(desc, str):
              raise InterpreterException('Description must be a string.')
  
          return name, val, desc
  
      @noArgsFlattening
      def set_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          self.held_object.values[name] = (val, desc)
  
      def set_quoted_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          if not isinstance(val, str):
              raise InterpreterException("Second argument to set_quoted must be a string.")
          escaped_val = '\\"'.join(val.split('"'))
          self.held_object.values[name] = ('"' + escaped_val + '"', desc)
  
      def set10_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          if val:
              self.held_object.values[name] = (1, desc)
          else:
              self.held_object.values[name] = (0, desc)
  
      def has_method(self, args, kwargs):
          return args[0] in self.held_object.values
  
      @FeatureNew('configuration_data.get()', '0.38.0')
      @noArgsFlattening
      def get_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              return self.held_object.get(name)[0]
          if len(args) > 1:
              return args[1]
          raise InterpreterException('Entry %s not in configuration data.' % name)
  
      @FeatureNew('configuration_data.get_unquoted()', '0.44.0')
      def get_unquoted_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              val = self.held_object.get(name)[0]
          elif len(args) > 1:
              val = args[1]
          else:
              raise InterpreterException('Entry %s not in configuration data.' % name)
          if val[0] == '"' and val[-1] == '"':
              return val[1:-1]
          return val
  
      def get(self, name):
          return self.held_object.values[name] # (val, desc)
  
      def keys(self):
          return self.held_object.values.keys()
  
      def merge_from_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Merge_from takes one positional argument.')
          from_object = args[0]
          if not isinstance(from_object, ConfigurationDataHolder):
              raise InterpreterException('Merge_from argument must be a configuration data object.')
          from_object = from_object.held_object
          for k, v in from_object.values.items():
              self.held_object.values[k] = v
  
  # Interpreter objects can not be pickled so we must have
  # these wrappers.
  
  class DependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               'name': self.name_method,
                               'get_pkgconfig_variable': self.pkgconfig_method,
                               'get_configtool_variable': self.configtool_method,
                               'get_variable': self.variable_method,
                               'partial_dependency': self.partial_dependency_method,
                               'include_type': self.include_type_method,
                               'as_system': self.as_system_method,
                               })
  
      def found(self):
          return self.found_method([], {})
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          if self.held_object.type_name == 'internal':
              return True
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @noPosargs
      @permittedKwargs({})
      def name_method(self, args, kwargs):
          return self.held_object.get_name()
  
      @permittedKwargs({'define_variable', 'default'})
      def pkgconfig_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_pkgconfig_variable(varname, kwargs)
  
      @FeatureNew('dep.get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
      @FeatureNew('dep.get_variable', '0.51.0')
      @noPosargs
      @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'})
      @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal'])
      def variable_method(self, args, kwargs):
          return self.held_object.get_variable(**kwargs)
  
      @FeatureNew('dep.include_type', '0.52.0')
      @noPosargs
      @permittedKwargs({})
      def include_type_method(self, args, kwargs):
          return self.held_object.get_include_type()
  
      @FeatureNew('dep.as_system', '0.52.0')
      @permittedKwargs({})
      def as_system_method(self, args, kwargs):
          args = listify(args)
          new_is_system = 'system'
          if len(args) > 1:
              raise InterpreterException('as_system takes only one optional value')
          if len(args) == 1:
              new_is_system = args[0]
          new_dep = self.held_object.generate_system_dependency(new_is_system)
          return DependencyHolder(new_dep, self.subproject)
  
  class ExternalProgramHolder(InterpreterObject, ObjectHolder):
      def __init__(self, ep, subproject, backend=None):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, ep)
          self.subproject = subproject
          self.backend = backend
          self.methods.update({'found': self.found_method,
                               'path': self.path_method,
                               'full_path': self.full_path_method})
          self.cached_version = None
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      @noPosargs
      @permittedKwargs({})
      def path_method(self, args, kwargs):
          mlog.deprecation('path() method is deprecated and replaced by full_path()')
          return self._full_path()
  
      @noPosargs
      @permittedKwargs({})
      @FeatureNew('ExternalProgram.full_path', '0.55.0')
      def full_path_method(self, args, kwargs):
          return self._full_path()
  
      def _full_path(self):
          exe = self.held_object
          if isinstance(exe, build.Executable):
              return self.backend.get_target_filename_abs(exe)
          return exe.get_path()
  
      def found(self):
          return isinstance(self.held_object, build.Executable) or self.held_object.found()
  
      def get_command(self):
          return self.held_object.get_command()
  
      def get_name(self):
          exe = self.held_object
          if isinstance(exe, build.Executable):
              return exe.name
          return exe.get_name()
  
      def get_version(self, interpreter):
          if isinstance(self.held_object, build.Executable):
              return self.held_object.project_version
          if not self.cached_version:
              raw_cmd = self.get_command() + ['--version']
              cmd = [self, '--version']
              res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True)
              if res.returncode != 0:
                  m = 'Running {!r} failed'
                  raise InterpreterException(m.format(raw_cmd))
              output = res.stdout.strip()
              if not output:
                  output = res.stderr.strip()
              match = re.search(r'([0-9][0-9\.]+)', output)
              if not match:
                  m = 'Could not find a version number in output of {!r}'
                  raise InterpreterException(m.format(raw_cmd))
              self.cached_version = match.group(1)
          return self.cached_version
  
  class ExternalLibraryHolder(InterpreterObject, ObjectHolder):
      def __init__(self, el, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      def found(self):
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def get_name(self):
          return self.held_object.name
  
      def get_compile_args(self):
          return self.held_object.get_compile_args()
  
      def get_link_args(self):
          return self.held_object.get_link_args()
  
      def get_exe_args(self):
          return self.held_object.get_exe_args()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      @FeatureNewKwargs('generator', '0.43.0', ['capture'])
      def __init__(self, interp, args, kwargs):
          self.interpreter = interp
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject)
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=None):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else []))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  # A machine that's statically known from the cross file
  class MachineHolder(InterpreterObject, ObjectHolder):
      def __init__(self, machine_info):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, machine_info)
          self.methods.update({'system': self.system_method,
                               'cpu': self.cpu_method,
                               'cpu_family': self.cpu_family_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  class IncludeDirsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, idobj):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, idobj)
  
  class Headers(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.install_subdir = kwargs.get('subdir', '')
          if os.path.isabs(self.install_subdir):
              mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.')
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InterpreterException('Custom_install_dir must be a string.')
  
      def set_install_subdir(self, subdir):
          self.install_subdir = subdir
  
      def get_install_subdir(self):
          return self.install_subdir
  
      def get_sources(self):
          return self.sources
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
  class DataHolder(InterpreterObject, ObjectHolder):
      def __init__(self, data):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, data)
  
      def get_source_subdir(self):
          return self.held_object.source_subdir
  
      def get_sources(self):
          return self.held_object.sources
  
      def get_install_dir(self):
          return self.held_object.install_dir
  
  class InstallDir(InterpreterObject):
      def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory):
          InterpreterObject.__init__(self)
          self.source_subdir = src_subdir
          self.installable_subdir = inst_subdir
          self.install_dir = install_dir
          self.install_mode = install_mode
          self.exclude = exclude
          self.strip_directory = strip_directory
  
  class Man(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.validate_sources()
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str):
              raise InterpreterException('Custom_install_dir must be a string.')
  
      def validate_sources(self):
          for s in self.sources:
              try:
                  num = int(s.split('.')[-1])
              except (IndexError, ValueError):
                  num = 0
              if num < 1 or num > 8:
                  raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
      def get_sources(self):
          return self.sources
  
  class GeneratedObjectsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, held_object):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, held_object)
  
  class TargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, target, interp):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, target, interp.subproject)
          self.interpreter = interp
  
  class BuildTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'extract_objects': self.extract_objects_method,
                               'extract_all_objects': self.extract_all_objects_method,
                               'name': self.name_method,
                               'get_id': self.get_id_method,
                               'outdir': self.outdir_method,
                               'full_path': self.full_path_method,
                               'private_dir_include': self.private_dir_include_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.filename)
  
      def is_cross(self):
          return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine)
  
      @noPosargs
      @permittedKwargs({})
      def private_dir_include_method(self, args, kwargs):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [self.interpreter.backend.get_target_private_dir(self.held_object)]))
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @noPosargs
      @permittedKwargs({})
      def outdir_method(self, args, kwargs):
          return self.interpreter.backend.get_target_dir(self.held_object)
  
      @permittedKwargs({})
      def extract_objects_method(self, args, kwargs):
          gobjs = self.held_object.extract_objects(args)
          return GeneratedObjectsHolder(gobjs)
  
      @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive'])
      @noPosargs
      @permittedKwargs({'recursive'})
      def extract_all_objects_method(self, args, kwargs):
          recursive = kwargs.get('recursive', False)
          gobjs = self.held_object.extract_all_objects(recursive)
          if gobjs.objlist and 'recursive' not in kwargs:
              mlog.warning('extract_all_objects called without setting recursive '
                           'keyword argument. Meson currently defaults to '
                           'non-recursive to maintain backward compatibility but '
                           'the default will be changed in the future.',
                           location=self.current_node)
          return GeneratedObjectsHolder(gobjs)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.held_object.get_id()
  
      @FeatureNew('name', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def name_method(self, args, kwargs):
          return self.held_object.name
  
  class ExecutableHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class StaticLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class SharedLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          # Set to True only when called from self.func_shared_lib().
          target.shared_library_only = False
  
  class BothLibrariesHolder(BuildTargetHolder):
      def __init__(self, shared_holder, static_holder, interp):
          # FIXME: This build target always represents the shared library, but
          # that should be configurable.
          super().__init__(shared_holder.held_object, interp)
          self.shared_holder = shared_holder
          self.static_holder = static_holder
          self.methods.update({'get_shared_lib': self.get_shared_lib_method,
                               'get_static_lib': self.get_static_lib_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}, {}: {}>'
          h1 = self.shared_holder.held_object
          h2 = self.static_holder.held_object
          return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
  
      @noPosargs
      @permittedKwargs({})
      def get_shared_lib_method(self, args, kwargs):
          return self.shared_holder
  
      @noPosargs
      @permittedKwargs({})
      def get_static_lib_method(self, args, kwargs):
          return self.static_holder
  
  class SharedModuleHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class JarHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class CustomTargetIndexHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               })
  
      @FeatureNew('custom_target[i].full_path', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
  class CustomTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               'to_list': self.to_list_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @FeatureNew('custom_target.to_list', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def to_list_method(self, args, kwargs):
          result = []
          for i in self.held_object:
              result.append(CustomTargetIndexHolder(i, self.interpreter))
          return result
  
      def __getitem__(self, index):
          return CustomTargetIndexHolder(self.held_object[index], self.interpreter)
  
      def __setitem__(self, index, value):  # lgtm[py/unexpected-raise-in-special-method]
          raise InterpreterException('Cannot set a member of a CustomTarget')
  
      def __delitem__(self, index):  # lgtm[py/unexpected-raise-in-special-method]
          raise InterpreterException('Cannot delete a member of a CustomTarget')
  
      def outdir_include(self):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))]))
  
  class RunTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
  class Test(InterpreterObject):
      def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable,
                   depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
                   is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables,
                   should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
                   priority: int):
          InterpreterObject.__init__(self)
          self.name = name
          self.suite = suite
          self.project_name = project
          self.exe = exe
          self.depends = depends
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.protocol = TestProtocol.from_str(protocol)
          self.priority = priority
  
      def get_exe(self):
          return self.exe
  
      def get_name(self):
          return self.name
  
  class SubprojectHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subinterpreter, subproject_dir, name, warnings=0, disabled_feature=None,
                   exception=None):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, subinterpreter)
          self.name = name
          self.warnings = warnings
          self.disabled_feature = disabled_feature
          self.exception = exception
          self.subproject_dir = subproject_dir
          self.methods.update({'get_variable': self.get_variable_method,
                               'found': self.found_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def found(self):
          return self.held_object is not None
  
      @permittedKwargs({})
      @noArgsFlattening
      def get_variable_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get_variable takes one or two arguments.')
          if not self.found():
              raise InterpreterException('Subproject "%s/%s" disabled can\'t get_variable on it.' % (
                  self.subproject_dir, self.name))
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Get_variable first argument must be a string.')
          try:
              return self.held_object.variables[varname]
          except KeyError:
              pass
  
          if len(args) == 2:
              return args[1]
  
          raise InvalidArguments('Requested variable "{0}" not found.'.format(varname))
  
  header_permitted_kwargs = set([
      'required',
      'prefix',
      'no_builtin_args',
      'include_directories',
      'args',
      'dependencies',
  ])
  
  find_library_permitted_kwargs = set([
      'has_headers',
      'required',
      'dirs',
      'static',
  ])
  
  find_library_permitted_kwargs |= set(['header_' + k for k in header_permitted_kwargs])
  
  class CompilerHolder(InterpreterObject):
      def __init__(self, compiler, env, subproject):
          InterpreterObject.__init__(self)
          self.compiler = compiler
          self.environment = env
          self.subproject = subproject
          self.methods.update({'compiles': self.compiles_method,
                               'links': self.links_method,
                               'get_id': self.get_id_method,
                               'get_linker_id': self.get_linker_id_method,
                               'compute_int': self.compute_int_method,
                               'sizeof': self.sizeof_method,
                               'get_define': self.get_define_method,
                               'check_header': self.check_header_method,
                               'has_header': self.has_header_method,
                               'has_header_symbol': self.has_header_symbol_method,
                               'run': self.run_method,
                               'has_function': self.has_function_method,
                               'has_member': self.has_member_method,
                               'has_members': self.has_members_method,
                               'has_type': self.has_type_method,
                               'alignment': self.alignment_method,
                               'version': self.version_method,
                               'cmd_array': self.cmd_array_method,
                               'find_library': self.find_library_method,
                               'has_argument': self.has_argument_method,
                               'has_function_attribute': self.has_func_attribute_method,
                               'get_supported_function_attributes': self.get_supported_function_attributes_method,
                               'has_multi_arguments': self.has_multi_arguments_method,
                               'get_supported_arguments': self.get_supported_arguments_method,
                               'first_supported_argument': self.first_supported_argument_method,
                               'has_link_argument': self.has_link_argument_method,
                               'has_multi_link_arguments': self.has_multi_link_arguments_method,
                               'get_supported_link_arguments': self.get_supported_link_arguments_method,
                               'first_supported_link_argument': self.first_supported_link_argument_method,
                               'unittest_args': self.unittest_args_method,
                               'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method,
                               'get_argument_syntax': self.get_argument_syntax_method,
                               })
  
      def _dep_msg(self, deps, endl):
          msg_single = 'with dependency {}'
          msg_many = 'with dependencies {}'
          if not deps:
              return endl
          if endl is None:
              endl = ''
          tpl = msg_many if len(deps) > 1 else msg_single
          names = []
          for d in deps:
              if isinstance(d, dependencies.ExternalLibrary):
                  name = '-l' + d.name
              else:
                  name = d.name
              names.append(name)
          return tpl.format(', '.join(names)) + endl
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.compiler.version
  
      @noPosargs
      @permittedKwargs({})
      def cmd_array_method(self, args, kwargs):
          return self.compiler.exelist
  
      def determine_args(self, kwargs, mode='link'):
          nobuiltins = kwargs.get('no_builtin_args', False)
          if not isinstance(nobuiltins, bool):
              raise InterpreterException('Type of no_builtin_args not a boolean.')
          args = []
          incdirs = extract_as_list(kwargs, 'include_directories')
          for i in incdirs:
              if not isinstance(i, IncludeDirsHolder):
                  raise InterpreterException('Include directories argument must be an include_directories object.')
              for idir in i.held_object.get_incdirs():
                  idir = os.path.join(self.environment.get_source_dir(),
                                      i.held_object.get_curdir(), idir)
                  args += self.compiler.get_include_args(idir, False)
          if not nobuiltins:
              for_machine = Interpreter.machine_from_native_kwarg(kwargs)
              opts = self.environment.coredata.compiler_options[for_machine][self.compiler.language]
              args += self.compiler.get_option_compile_args(opts)
              if mode == 'link':
                  args += self.compiler.get_option_link_args(opts)
          args += mesonlib.stringlistify(kwargs.get('args', []))
          return args
  
      def determine_dependencies(self, kwargs, endl=':'):
          deps = kwargs.get('dependencies', None)
          if deps is not None:
              deps = listify(deps)
              final_deps = []
              for d in deps:
                  try:
                      d = d.held_object
                  except Exception:
                      pass
                  if isinstance(d, InternalDependency) or not isinstance(d, Dependency):
                      raise InterpreterException('Dependencies must be external dependencies')
                  final_deps.append(d)
              deps = final_deps
          return deps, self._dep_msg(deps, endl)
  
      @permittedKwargs({
          'prefix',
          'args',
          'dependencies',
      })
      def alignment_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Alignment method takes exactly one positional argument.')
          check_stringlist(args)
          typename = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of alignment must be a string.')
          extra_args = mesonlib.stringlistify(kwargs.get('args', []))
          deps, msg = self.determine_dependencies(kwargs)
          result = self.compiler.alignment(typename, prefix, self.environment,
                                           extra_args=extra_args,
                                           dependencies=deps)
          mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result)
          return result
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def run_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Run method takes exactly one positional argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result = self.compiler.run(code, self.environment, extra_args=extra_args,
                                     dependencies=deps)
          if len(testname) > 0:
              if not result.compiled:
                  h = mlog.red('DID NOT COMPILE')
              elif result.returncode == 0:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO (%d)' % result.returncode)
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h)
          return TryRunResultHolder(result)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.compiler.get_id()
  
      @noPosargs
      @permittedKwargs({})
      @FeatureNew('compiler.get_linker_id', '0.53.0')
      def get_linker_id_method(self, args, kwargs):
          return self.compiler.get_linker_id()
  
      @noPosargs
      @permittedKwargs({})
      def symbols_have_underscore_prefix_method(self, args, kwargs):
          '''
          Check if the compiler prefixes _ (underscore) to global C symbols
          See: https://en.wikipedia.org/wiki/Name_mangling#C
          '''
          return self.compiler.symbols_have_underscore_prefix(self.environment)
  
      @noPosargs
      @permittedKwargs({})
      def unittest_args_method(self, args, kwargs):
          '''
          This function is deprecated and should not be used.
          It can be removed in a future version of Meson.
          '''
          if not hasattr(self.compiler, 'get_feature_args'):
              raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language()))
          build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir())
          return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src)
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_member_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Has_member takes exactly two arguments.')
          check_stringlist(args)
          typename, membername = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_member must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_members(typename, [membername], prefix,
                                                  self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking whether type', mlog.bold(typename, True),
                   'has member', mlog.bold(membername, True), msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_members_method(self, args, kwargs):
          if len(args) < 2:
              raise InterpreterException('Has_members needs at least two arguments.')
          check_stringlist(args)
          typename, *membernames = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_members must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_members(typename, membernames, prefix,
                                                  self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          members = mlog.bold(', '.join(['"{}"'.format(m) for m in membernames]))
          mlog.log('Checking whether type', mlog.bold(typename, True),
                   'has members', members, msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_function_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Has_function takes exactly one argument.')
          check_stringlist(args)
          funcname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_function must be a string.')
          extra_args = self.determine_args(kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_function(funcname, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking for function', mlog.bold(funcname, True), msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_type_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Has_type takes exactly one argument.')
          check_stringlist(args)
          typename = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_type must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_type(typename, prefix, self.environment,
                                               extra_args=extra_args, dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking for type', mlog.bold(typename, True), msg, hadtxt, cached)
          return had
  
      @FeatureNew('compiler.compute_int', '0.40.0')
      @permittedKwargs({
          'prefix',
          'low',
          'high',
          'guess',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def compute_int_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Compute_int takes exactly one argument.')
          check_stringlist(args)
          expression = args[0]
          prefix = kwargs.get('prefix', '')
          low = kwargs.get('low', None)
          high = kwargs.get('high', None)
          guess = kwargs.get('guess', None)
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of compute_int must be a string.')
          if low is not None and not isinstance(low, int):
              raise InterpreterException('Low argument of compute_int must be an int.')
          if high is not None and not isinstance(high, int):
              raise InterpreterException('High argument of compute_int must be an int.')
          if guess is not None and not isinstance(guess, int):
              raise InterpreterException('Guess argument of compute_int must be an int.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          res = self.compiler.compute_int(expression, low, high, guess, prefix,
                                          self.environment, extra_args=extra_args,
                                          dependencies=deps)
          mlog.log('Computing int of', mlog.bold(expression, True), msg, res)
          return res
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def sizeof_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Sizeof takes exactly one argument.')
          check_stringlist(args)
          element = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of sizeof must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          esize = self.compiler.sizeof(element, prefix, self.environment,
                                       extra_args=extra_args, dependencies=deps)
          mlog.log('Checking for size of', mlog.bold(element, True), msg, esize)
          return esize
  
      @FeatureNew('compiler.get_define', '0.40.0')
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def get_define_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('get_define() takes exactly one argument.')
          check_stringlist(args)
          element = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of get_define() must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          value, cached = self.compiler.get_define(element, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached)
          return value
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def compiles_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('compiles method takes exactly one argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result, cached = self.compiler.compiles(code, self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          if len(testname) > 0:
              if result:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO')
              cached = mlog.blue('(cached)') if cached else ''
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'compiles:', h, cached)
          return result
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def links_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('links method takes exactly one argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result, cached = self.compiler.links(code, self.environment,
                                               extra_args=extra_args,
                                               dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if len(testname) > 0:
              if result:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO')
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached)
          return result
  
      @FeatureNew('compiler.check_header', '0.47.0')
      @FeatureNewKwargs('compiler.check_header', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def check_header_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('check_header method takes exactly one argument.')
          check_stringlist(args)
          hname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.check_header(hname, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if required and not haz:
              raise InterpreterException('{} header {!r} not usable'.format(self.compiler.get_display_language(), hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log('Check usable header', mlog.bold(hname, True), msg, h, cached)
          return haz
  
      @FeatureNewKwargs('compiler.has_header', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def has_header_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('has_header method takes exactly one argument.')
          check_stringlist(args)
          hname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.has_header(hname, prefix, self.environment,
                                                 extra_args=extra_args, dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if required and not haz:
              raise InterpreterException('{} header {!r} not found'.format(self.compiler.get_display_language(), hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log('Has header', mlog.bold(hname, True), msg, h, cached)
          return haz
  
      @FeatureNewKwargs('compiler.has_header_symbol', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def has_header_symbol_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('has_header_symbol method takes exactly two arguments.')
          check_stringlist(args)
          hname, symbol = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header_symbol must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment,
                                                        extra_args=extra_args,
                                                        dependencies=deps)
          if required and not haz:
              raise InterpreterException('{} symbol {} not found in header {}'.format(self.compiler.get_display_language(), symbol, hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), msg, h, cached)
          return haz
  
      def notfound_library(self, libname):
          lib = dependencies.ExternalLibrary(libname, None,
                                             self.environment,
                                             self.compiler.language,
                                             silent=True)
          return ExternalLibraryHolder(lib, self.subproject)
  
      @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static'])
      @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers'])
      @FeatureNewKwargs('compiler.find_library', '0.49.0', ['disabler'])
      @disablerIfNotFound
      @permittedKwargs(find_library_permitted_kwargs)
      def find_library_method(self, args, kwargs):
          # TODO add dependencies support?
          if len(args) != 1:
              raise InterpreterException('find_library method takes one argument.')
          libname = args[0]
          if not isinstance(libname, str):
              raise InterpreterException('Library name not a string.')
  
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.notfound_library(libname)
  
          has_header_kwargs = {k[7:]: v for k, v in kwargs.items() if k.startswith('header_')}
          has_header_kwargs['required'] = required
          headers = mesonlib.stringlistify(kwargs.get('has_headers', []))
          for h in headers:
              if not self.has_header_method([h], has_header_kwargs):
                  return self.notfound_library(libname)
  
          search_dirs = extract_search_dirs(kwargs)
  
          libtype = mesonlib.LibType.PREFER_SHARED
          if 'static' in kwargs:
              if not isinstance(kwargs['static'], bool):
                  raise InterpreterException('static must be a boolean')
              libtype = mesonlib.LibType.STATIC if kwargs['static'] else mesonlib.LibType.SHARED
          linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype)
          if required and not linkargs:
              raise InterpreterException(
                  '{} library {!r} not found'.format(self.compiler.get_display_language(), libname))
          lib = dependencies.ExternalLibrary(libname, linkargs, self.environment,
                                             self.compiler.language)
          return ExternalLibraryHolder(lib, self.subproject)
  
      @permittedKwargs({})
      def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool:
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_argument takes exactly one argument.')
          return self.has_multi_arguments_method(args, kwargs)
  
      @permittedKwargs({})
      def has_multi_arguments_method(self, args: T.Sequence[str], kwargs: dict):
          args = mesonlib.stringlistify(args)
          result, cached = self.compiler.has_multi_arguments(args, self.environment)
          if result:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log(
              'Compiler for {} supports arguments {}:'.format(
                  self.compiler.get_display_language(), ' '.join(args)),
              h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_arguments', '0.43.0')
      @permittedKwargs({})
      def get_supported_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          supported_args = []
          for arg in args:
              if self.has_argument_method(arg, kwargs):
                  supported_args.append(arg)
          return supported_args
  
      @permittedKwargs({})
      def first_supported_argument_method(self, args: T.Sequence[str], kwargs: dict) -> T.List[str]:
          for arg in mesonlib.stringlistify(args):
              if self.has_argument_method(arg, kwargs):
                  mlog.log('First supported argument:', mlog.bold(arg))
                  return [arg]
          mlog.log('First supported argument:', mlog.red('None'))
          return []
  
      @FeatureNew('compiler.has_link_argument', '0.46.0')
      @permittedKwargs({})
      def has_link_argument_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_link_argument takes exactly one argument.')
          return self.has_multi_link_arguments_method(args, kwargs)
  
      @FeatureNew('compiler.has_multi_link_argument', '0.46.0')
      @permittedKwargs({})
      def has_multi_link_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          result, cached = self.compiler.has_multi_link_arguments(args, self.environment)
          cached = mlog.blue('(cached)') if cached else ''
          if result:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log(
              'Compiler for {} supports link arguments {}:'.format(
                  self.compiler.get_display_language(), ' '.join(args)),
              h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_link_arguments_method', '0.46.0')
      @permittedKwargs({})
      def get_supported_link_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          supported_args = []
          for arg in args:
              if self.has_link_argument_method(arg, kwargs):
                  supported_args.append(arg)
          return supported_args
  
      @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0')
      @permittedKwargs({})
      def first_supported_link_argument_method(self, args, kwargs):
          for i in mesonlib.stringlistify(args):
              if self.has_link_argument_method(i, kwargs):
                  mlog.log('First supported link argument:', mlog.bold(i))
                  return [i]
          mlog.log('First supported link argument:', mlog.red('None'))
          return []
  
      @FeatureNew('compiler.has_function_attribute', '0.48.0')
      @permittedKwargs({})
      def has_func_attribute_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_func_attribute takes exactly one argument.')
          result, cached = self.compiler.has_func_attribute(args[0], self.environment)
          cached = mlog.blue('(cached)') if cached else ''
          h = mlog.green('YES') if result else mlog.red('NO')
          mlog.log('Compiler for {} supports function attribute {}:'.format(self.compiler.get_display_language(), args[0]), h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_function_attributes', '0.48.0')
      @permittedKwargs({})
      def get_supported_function_attributes_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          return [a for a in args if self.has_func_attribute_method(a, kwargs)]
  
      @FeatureNew('compiler.get_argument_syntax_method', '0.49.0')
      @noPosargs
      @noKwargs
      def get_argument_syntax_method(self, args, kwargs):
          return self.compiler.get_argument_syntax()
  
  
  ModuleState = collections.namedtuple('ModuleState', [
      'source_root', 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment',
      'project_name', 'project_version', 'backend', 'targets',
      'data', 'headers', 'man', 'global_args', 'project_args', 'build_machine',
      'host_machine', 'target_machine', 'current_node'])
  
  class ModuleHolder(InterpreterObject, ObjectHolder):
      def __init__(self, modname, module, interpreter):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, module)
          self.modname = modname
          self.interpreter = interpreter
  
      def method_call(self, method_name, args, kwargs):
          try:
              fn = getattr(self.held_object, method_name)
          except AttributeError:
              raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name))
          if method_name.startswith('_'):
              raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname))
          if not getattr(fn, 'no-args-flattening', False):
              args = flatten(args)
          # This is not 100% reliable but we can't use hash()
          # because the Build object contains dicts and lists.
          num_targets = len(self.interpreter.build.targets)
          state = ModuleState(
              source_root = self.interpreter.environment.get_source_dir(),
              build_to_src=mesonlib.relpath(self.interpreter.environment.get_source_dir(),
                                            self.interpreter.environment.get_build_dir()),
              subproject=self.interpreter.subproject,
              subdir=self.interpreter.subdir,
              current_lineno=self.interpreter.current_lineno,
              environment=self.interpreter.environment,
              project_name=self.interpreter.build.project_name,
              project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname],
              # The backend object is under-used right now, but we will need it:
              # https://github.com/mesonbuild/meson/issues/1419
              backend=self.interpreter.backend,
              targets=self.interpreter.build.targets,
              data=self.interpreter.build.data,
              headers=self.interpreter.build.get_headers(),
              man=self.interpreter.build.get_man(),
              #global_args_for_build = self.interpreter.build.global_args.build,
              global_args = self.interpreter.build.global_args.host,
              #project_args_for_build = self.interpreter.build.projects_args.build.get(self.interpreter.subproject, {}),
              project_args = self.interpreter.build.projects_args.host.get(self.interpreter.subproject, {}),
              build_machine=self.interpreter.builtin['build_machine'].held_object,
              host_machine=self.interpreter.builtin['host_machine'].held_object,
              target_machine=self.interpreter.builtin['target_machine'].held_object,
              current_node=self.current_node
          )
          # Many modules do for example self.interpreter.find_program_impl(),
          # so we have to ensure they use the current interpreter and not the one
          # that first imported that module, otherwise it will use outdated
          # overrides.
          self.held_object.interpreter = self.interpreter
          if self.held_object.is_snippet(method_name):
              value = fn(self.interpreter, state, args, kwargs)
              return self.interpreter.holderify(value)
          else:
              value = fn(state, args, kwargs)
              if num_targets != len(self.interpreter.build.targets):
                  raise InterpreterException('Extension module altered internal state illegally.')
              return self.interpreter.module_method_callback(value)
  
  
  class Summary:
      def __init__(self, project_name, project_version):
          self.project_name = project_name
          self.project_version = project_version
          self.sections = collections.defaultdict(dict)
          self.max_key_len = 0
  
      def add_section(self, section, values, kwargs):
          bool_yn = kwargs.get('bool_yn', False)
          if not isinstance(bool_yn, bool):
              raise InterpreterException('bool_yn keyword argument must be boolean')
          list_sep = kwargs.get('list_sep')
          if list_sep is not None and not isinstance(list_sep, str):
              raise InterpreterException('list_sep keyword argument must be string')
          for k, v in values.items():
              if k in self.sections[section]:
                  raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k))
              formatted_values = []
              for i in listify(v):
                  if not isinstance(i, (str, int)):
                      m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean'
                      raise InterpreterException(m.format(section, k))
                  if bool_yn and isinstance(i, bool):
                      formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
                  else:
                      formatted_values.append(i)
              self.sections[section][k] = (formatted_values, list_sep)
              self.max_key_len = max(self.max_key_len, len(k))
  
      def dump(self):
          mlog.log(self.project_name, mlog.normal_cyan(self.project_version))
          for section, values in self.sections.items():
              mlog.log('')  # newline
              if section:
                  mlog.log(' ', mlog.bold(section))
              for k, v in values.items():
                  v, list_sep = v
                  indent = self.max_key_len - len(k) + 3
                  end = ' ' if v else ''
                  mlog.log(' ' * indent, k + ':', end=end)
                  if list_sep is None:
                      indent = self.max_key_len + 6
                      list_sep = '\n' + ' ' * indent
                  mlog.log(*v, sep=list_sep)
          mlog.log('')  # newline
  
  
  class MesonMain(InterpreterObject):
      def __init__(self, build, interpreter):
          InterpreterObject.__init__(self)
          self.build = build
          self.interpreter = interpreter
          self._found_source_scripts = {}
          self.methods.update({'get_compiler': self.get_compiler_method,
                               'is_cross_build': self.is_cross_build_method,
                               'has_exe_wrapper': self.has_exe_wrapper_method,
                               'is_unity': self.is_unity_method,
                               'is_subproject': self.is_subproject_method,
                               'current_source_dir': self.current_source_dir_method,
                               'current_build_dir': self.current_build_dir_method,
                               'source_root': self.source_root_method,
                               'build_root': self.build_root_method,
                               'add_install_script': self.add_install_script_method,
                               'add_postconf_script': self.add_postconf_script_method,
                               'add_dist_script': self.add_dist_script_method,
                               'install_dependency_manifest': self.install_dependency_manifest_method,
                               'override_dependency': self.override_dependency_method,
                               'override_find_program': self.override_find_program_method,
                               'project_version': self.project_version_method,
                               'project_license': self.project_license_method,
                               'version': self.version_method,
                               'project_name': self.project_name_method,
                               'get_cross_property': self.get_cross_property_method,
                               'get_external_property': self.get_external_property_method,
                               'backend': self.backend_method,
                               })
  
      def _find_source_script(self, name, args):
          # Prefer scripts in the current source directory
          search_dir = os.path.join(self.interpreter.environment.source_dir,
                                    self.interpreter.subdir)
          key = (name, search_dir)
          if key in self._found_source_scripts:
              found = self._found_source_scripts[key]
          else:
              found = dependencies.ExternalProgram(name, search_dir=search_dir)
              if found.found():
                  self._found_source_scripts[key] = found
              else:
                  m = 'Script or command {!r} not found or not executable'
                  raise InterpreterException(m.format(name))
          return build.RunScript(found.get_command(), args)
  
      @permittedKwargs({})
      def add_install_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_install_script takes one or more arguments')
          check_stringlist(args, 'add_install_script args must be strings')
          script = self._find_source_script(args[0], args[1:])
          self.build.install_scripts.append(script)
  
      @permittedKwargs({})
      def add_postconf_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_postconf_script takes one or more arguments')
          check_stringlist(args, 'add_postconf_script arguments must be strings')
          script = self._find_source_script(args[0], args[1:])
          self.build.postconf_scripts.append(script)
  
      @permittedKwargs({})
      def add_dist_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_dist_script takes one or more arguments')
          if len(args) > 1:
              FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject)
          check_stringlist(args, 'add_dist_script argument must be a string')
          if self.interpreter.subproject != '':
              raise InterpreterException('add_dist_script may not be used in a subproject.')
          script = self._find_source_script(args[0], args[1:])
          self.build.dist_scripts.append(script)
  
      @noPosargs
      @permittedKwargs({})
      def current_source_dir_method(self, args, kwargs):
          src = self.interpreter.environment.source_dir
          sub = self.interpreter.subdir
          if sub == '':
              return src
          return os.path.join(src, sub)
  
      @noPosargs
      @permittedKwargs({})
      def current_build_dir_method(self, args, kwargs):
          src = self.interpreter.environment.build_dir
          sub = self.interpreter.subdir
          if sub == '':
              return src
          return os.path.join(src, sub)
  
      @noPosargs
      @permittedKwargs({})
      def backend_method(self, args, kwargs):
          return self.interpreter.backend.name
  
      @noPosargs
      @permittedKwargs({})
      def source_root_method(self, args, kwargs):
          return self.interpreter.environment.source_dir
  
      @noPosargs
      @permittedKwargs({})
      def build_root_method(self, args, kwargs):
          return self.interpreter.environment.build_dir
  
      @noPosargs
      @permittedKwargs({})
      def has_exe_wrapper_method(self, args, kwargs):
          if self.is_cross_build_method(None, None) and \
             self.build.environment.need_exe_wrapper():
              if self.build.environment.exe_wrapper is None:
                  return False
          # We return True when exe_wrap is defined, when it's not needed, and
          # when we're compiling natively. The last two are semantically confusing.
          # Need to revisit this.
          return True
  
      @noPosargs
      @permittedKwargs({})
      def is_cross_build_method(self, args, kwargs):
          return self.build.environment.is_cross_build()
  
      @permittedKwargs({'native'})
      def get_compiler_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('get_compiler_method must have one and only one argument.')
          cname = args[0]
          for_machine = Interpreter.machine_from_native_kwarg(kwargs)
          clist = self.interpreter.coredata.compilers[for_machine]
          if cname in clist:
              return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject)
          raise InterpreterException('Tried to access compiler for unspecified language "%s".' % cname)
  
      @noPosargs
      @permittedKwargs({})
      def is_unity_method(self, args, kwargs):
          optval = self.interpreter.environment.coredata.get_builtin_option('unity')
          if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()):
              return True
          return False
  
      @noPosargs
      @permittedKwargs({})
      def is_subproject_method(self, args, kwargs):
          return self.interpreter.is_subproject()
  
      @permittedKwargs({})
      def install_dependency_manifest_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Must specify manifest install file name')
          if not isinstance(args[0], str):
              raise InterpreterException('Argument must be a string.')
          self.build.dep_manifest_name = args[0]
  
      @FeatureNew('meson.override_find_program', '0.46.0')
      @permittedKwargs({})
      def override_find_program_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Override needs two arguments')
          name, exe = args
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string')
          if hasattr(exe, 'held_object'):
              exe = exe.held_object
          if isinstance(exe, mesonlib.File):
              abspath = exe.absolute_path(self.interpreter.environment.source_dir,
                                          self.interpreter.environment.build_dir)
              if not os.path.exists(abspath):
                  raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
              exe = OverrideProgram(abspath)
          if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)):
              raise InterpreterException('Second argument must be an external program or executable.')
          self.interpreter.add_find_program_override(name, exe)
  
      @FeatureNew('meson.override_dependency', '0.54.0')
      @permittedKwargs({'native'})
      def override_dependency_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Override needs two arguments')
          name = args[0]
          dep = args[1]
          if not isinstance(name, str) or not name:
              raise InterpreterException('First argument must be a string and cannot be empty')
          if hasattr(dep, 'held_object'):
              dep = dep.held_object
          if not isinstance(dep, dependencies.Dependency):
              raise InterpreterException('Second argument must be a dependency object')
          identifier = dependencies.get_dep_identifier(name, kwargs)
          for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
          override = self.build.dependency_overrides[for_machine].get(identifier)
          if override:
              m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}'
              location = mlog.get_error_location_string(override.node.filename, override.node.lineno)
              raise InterpreterException(m.format(name, location))
          self.build.dependency_overrides[for_machine][identifier] = \
              build.DependencyOverride(dep, self.interpreter.current_node)
  
      @noPosargs
      @permittedKwargs({})
      def project_version_method(self, args, kwargs):
          return self.build.dep_manifest[self.interpreter.active_projectname]['version']
  
      @FeatureNew('meson.project_license()', '0.45.0')
      @noPosargs
      @permittedKwargs({})
      def project_license_method(self, args, kwargs):
          return self.build.dep_manifest[self.interpreter.active_projectname]['license']
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return coredata.version
  
      @noPosargs
      @permittedKwargs({})
      def project_name_method(self, args, kwargs):
          return self.interpreter.active_projectname
  
      @noArgsFlattening
      @permittedKwargs({})
      def get_cross_property_method(self, args, kwargs) -> str:
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Must have one or two arguments.')
          propname = args[0]
          if not isinstance(propname, str):
              raise InterpreterException('Property name must be string.')
          try:
              props = self.interpreter.environment.properties.host
              return props[propname]
          except Exception:
              if len(args) == 2:
                  return args[1]
              raise InterpreterException('Unknown cross property: %s.' % propname)
  
      @noArgsFlattening
      @permittedKwargs({'native'})
      @FeatureNew('meson.get_external_property', '0.54.0')
      def get_external_property_method(self, args: T.Sequence[str], kwargs: dict) -> str:
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Must have one or two positional arguments.')
          propname = args[0]
          if not isinstance(propname, str):
              raise InterpreterException('Property name must be string.')
  
          def _get_native() -> str:
              try:
                  props = self.interpreter.environment.properties.build
                  return props[propname]
              except Exception:
                  if len(args) == 2:
                      return args[1]
                  raise InterpreterException('Unknown native property: %s.' % propname)
          if 'native' in kwargs:
              if kwargs['native']:
                  return _get_native()
              else:
                  return self.get_cross_property_method(args, {})
          else:  # native: not specified
              if self.build.environment.is_cross_build():
                  return self.get_cross_property_method(args, kwargs)
              else:
                  return _get_native()
  
  known_library_kwargs = (
      build.known_shlib_kwargs |
      build.known_stlib_kwargs
  )
  
  known_build_target_kwargs = (
      known_library_kwargs |
      build.known_exe_kwargs |
      build.known_jar_kwargs |
      {'target_type'}
  )
  
  _base_test_args = {'args', 'depends', 'env', 'should_fail', 'timeout', 'workdir', 'suite', 'priority', 'protocol'}
  
  permitted_kwargs = {'add_global_arguments': {'language', 'native'},
                      'add_global_link_arguments': {'language', 'native'},
                      'add_languages': {'required', 'native'},
                      'add_project_link_arguments': {'language', 'native'},
                      'add_project_arguments': {'language', 'native'},
                      'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default'},
                      'benchmark': _base_test_args,
                      'build_target': known_build_target_kwargs,
                      'configure_file': {'input',
                                         'output',
                                         'configuration',
                                         'command',
                                         'copy',
                                         'depfile',
                                         'install_dir',
                                         'install_mode',
                                         'capture',
                                         'install',
                                         'format',
                                         'output_format',
                                         'encoding'},
                      'custom_target': {'input',
                                        'output',
                                        'command',
                                        'install',
                                        'install_dir',
                                        'install_mode',
                                        'build_always',
                                        'capture',
                                        'depends',
                                        'depend_files',
                                        'depfile',
                                        'build_by_default',
                                        'build_always_stale',
                                        'console'},
                      'dependency': {'default_options',
                                     'embed',
                                     'fallback',
                                     'language',
                                     'main',
                                     'method',
                                     'modules',
                                     'components',
                                     'cmake_module_path',
                                     'optional_modules',
                                     'native',
                                     'not_found_message',
                                     'required',
                                     'static',
                                     'version',
                                     'private_headers',
                                     'cmake_args',
                                     'include_type',
                                     },
                      'declare_dependency': {'include_directories',
                                             'link_with',
                                             'sources',
                                             'dependencies',
                                             'compile_args',
                                             'link_args',
                                             'link_whole',
                                             'version',
                                             'variables',
                                             },
                      'executable': build.known_exe_kwargs,
                      'find_program': {'required', 'native', 'version', 'dirs'},
                      'generator': {'arguments',
                                    'output',
                                    'depends',
                                    'depfile',
                                    'capture',
                                    'preserve_path_from'},
                      'include_directories': {'is_system'},
                      'install_data': {'install_dir', 'install_mode', 'rename', 'sources'},
                      'install_headers': {'install_dir', 'install_mode', 'subdir'},
                      'install_man': {'install_dir', 'install_mode'},
                      'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'},
                      'jar': build.known_jar_kwargs,
                      'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
                      'run_command': {'check', 'capture', 'env'},
                      'run_target': {'command', 'depends'},
                      'shared_library': build.known_shlib_kwargs,
                      'shared_module': build.known_shmod_kwargs,
                      'static_library': build.known_stlib_kwargs,
                      'both_libraries': known_library_kwargs,
                      'library': known_library_kwargs,
                      'subdir': {'if_found'},
                      'subproject': {'version', 'default_options', 'required'},
                      'test': set.union(_base_test_args, {'is_parallel'}),
                      'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'},
                      }
  
  
  class Interpreter(InterpreterBase):
  
      def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects',
                   modules = None, default_project_options=None, mock=False, ast=None):
          super().__init__(build.environment.get_source_dir(), subdir, subproject)
          self.an_unpicklable_object = mesonlib.an_unpicklable_object
          self.build = build
          self.environment = build.environment
          self.coredata = self.environment.get_coredata()
          self.backend = backend
          self.summary = {}
          if modules is None:
              self.modules = {}
          else:
              self.modules = modules
          # Subproject directory is usually the name of the subproject, but can
          # be different for dependencies provided by wrap files.
          self.subproject_directory_name = subdir.split(os.path.sep)[-1]
          self.subproject_dir = subproject_dir
          self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
          if not mock and ast is None:
              self.load_root_meson_file()
              self.sanity_check_ast()
          elif ast is not None:
              self.ast = ast
              self.sanity_check_ast()
          self.builtin.update({'meson': MesonMain(build, self)})
          self.generators = []
          self.visited_subdirs = {}
          self.project_args_frozen = False
          self.global_args_frozen = False  # implies self.project_args_frozen
          self.subprojects = {}
          self.subproject_stack = []
          self.configure_file_outputs = {}
          # Passed from the outside, only used in subprojects.
          if default_project_options:
              self.default_project_options = default_project_options.copy()
          else:
              self.default_project_options = {}
          self.project_default_options = {}
          self.build_func_dict()
          # build_def_files needs to be defined before parse_project is called
          self.build_def_files = [os.path.join(self.subdir, environment.build_filename)]
          if not mock:
              self.parse_project()
          self._redetect_machines()
  
      def _redetect_machines(self):
          # Re-initialize machine descriptions. We can do a better job now because we
          # have the compilers needed to gain more knowledge, so wipe out old
          # inference and start over.
          machines = self.build.environment.machines.miss_defaulting()
          machines.build = environment.detect_machine_info(self.coredata.compilers.build)
          self.build.environment.machines = machines.default_missing()
          assert self.build.environment.machines.build.cpu is not None
          assert self.build.environment.machines.host.cpu is not None
          assert self.build.environment.machines.target.cpu is not None
  
          self.builtin['build_machine'] = \
              MachineHolder(self.build.environment.machines.build)
          self.builtin['host_machine'] = \
              MachineHolder(self.build.environment.machines.host)
          self.builtin['target_machine'] = \
              MachineHolder(self.build.environment.machines.target)
  
      def get_non_matching_default_options(self):
          env = self.environment
          for def_opt_name, def_opt_value in self.project_default_options.items():
              for opts in env.coredata.get_all_options():
                  cur_opt_value = opts.get(def_opt_name)
                  if cur_opt_value is not None:
                      def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value)
                      if def_opt_value != cur_opt_value.value:
                          yield (def_opt_name, def_opt_value, cur_opt_value)
  
      def build_func_dict(self):
          self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
                             'add_project_arguments': self.func_add_project_arguments,
                             'add_global_link_arguments': self.func_add_global_link_arguments,
                             'add_project_link_arguments': self.func_add_project_link_arguments,
                             'add_test_setup': self.func_add_test_setup,
                             'add_languages': self.func_add_languages,
                             'alias_target': self.func_alias_target,
                             'assert': self.func_assert,
                             'benchmark': self.func_benchmark,
                             'build_target': self.func_build_target,
                             'configuration_data': self.func_configuration_data,
                             'configure_file': self.func_configure_file,
                             'custom_target': self.func_custom_target,
                             'declare_dependency': self.func_declare_dependency,
                             'dependency': self.func_dependency,
                             'disabler': self.func_disabler,
                             'environment': self.func_environment,
                             'error': self.func_error,
                             'executable': self.func_executable,
                             'generator': self.func_generator,
                             'gettext': self.func_gettext,
                             'get_option': self.func_get_option,
                             'get_variable': self.func_get_variable,
                             'files': self.func_files,
                             'find_library': self.func_find_library,
                             'find_program': self.func_find_program,
                             'include_directories': self.func_include_directories,
                             'import': self.func_import,
                             'install_data': self.func_install_data,
                             'install_headers': self.func_install_headers,
                             'install_man': self.func_install_man,
                             'install_subdir': self.func_install_subdir,
                             'is_disabler': self.func_is_disabler,
                             'is_variable': self.func_is_variable,
                             'jar': self.func_jar,
                             'join_paths': self.func_join_paths,
                             'library': self.func_library,
                             'message': self.func_message,
                             'warning': self.func_warning,
                             'option': self.func_option,
                             'project': self.func_project,
                             'run_target': self.func_run_target,
                             'run_command': self.func_run_command,
                             'set_variable': self.func_set_variable,
                             'subdir': self.func_subdir,
                             'subdir_done': self.func_subdir_done,
                             'subproject': self.func_subproject,
                             'summary': self.func_summary,
                             'shared_library': self.func_shared_lib,
                             'shared_module': self.func_shared_module,
                             'static_library': self.func_static_lib,
                             'both_libraries': self.func_both_lib,
                             'test': self.func_test,
                             'vcs_tag': self.func_vcs_tag
                             })
          if 'MESON_UNIT_TEST' in os.environ:
              self.funcs.update({'exception': self.func_exception})
  
      def holderify(self, item):
          if isinstance(item, list):
              return [self.holderify(x) for x in item]
          if isinstance(item, dict):
              return {k: self.holderify(v) for k, v in item.items()}
  
          if isinstance(item, build.CustomTarget):
              return CustomTargetHolder(item, self)
          elif isinstance(item, (int, str, bool, Disabler)) or item is None:
              return item
          elif isinstance(item, build.Executable):
              return ExecutableHolder(item, self)
          elif isinstance(item, build.GeneratedList):
              return GeneratedListHolder(item)
          elif isinstance(item, build.RunTarget):
              raise RuntimeError('This is not a pipe.')
          elif isinstance(item, build.RunScript):
              raise RuntimeError('Do not do this.')
          elif isinstance(item, build.Data):
              return DataHolder(item)
          elif isinstance(item, dependencies.Dependency):
              return DependencyHolder(item, self.subproject)
          elif isinstance(item, dependencies.ExternalProgram):
              return ExternalProgramHolder(item, self.subproject)
          elif hasattr(item, 'held_object'):
              return item
          else:
              raise InterpreterException('Module returned a value of unknown type.')
  
      def process_new_values(self, invalues):
          invalues = listify(invalues)
          for v in invalues:
              if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)):
                  v = v.held_object
  
              if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)):
                  self.add_target(v.name, v)
              elif isinstance(v, list):
                  self.module_method_callback(v)
              elif isinstance(v, build.GeneratedList):
                  pass
              elif isinstance(v, build.RunScript):
                  self.build.install_scripts.append(v)
              elif isinstance(v, build.Data):
                  self.build.data.append(v)
              elif isinstance(v, dependencies.ExternalProgram):
                  return ExternalProgramHolder(v, self.subproject)
              elif isinstance(v, dependencies.InternalDependency):
                  # FIXME: This is special cased and not ideal:
                  # The first source is our new VapiTarget, the rest are deps
                  self.process_new_values(v.sources[0])
              elif hasattr(v, 'held_object'):
                  pass
              elif isinstance(v, (int, str, bool, Disabler)):
                  pass
              else:
                  raise InterpreterException('Module returned a value of unknown type.')
  
      def module_method_callback(self, return_object):
          if not isinstance(return_object, ModuleReturnValue):
              raise InterpreterException('Bug in module, it returned an invalid object')
          invalues = return_object.new_objects
          self.process_new_values(invalues)
          return self.holderify(return_object.return_value)
  
      def get_build_def_files(self):
          return self.build_def_files
  
      def add_build_def_file(self, f):
          # Use relative path for files within source directory, and absolute path
          # for system files. Skip files within build directory. Also skip not regular
          # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this
          # is especially important to convert '/' to '\' on Windows.
          if isinstance(f, mesonlib.File):
              if f.is_built:
                  return
              f = os.path.normpath(f.relative_name())
          elif os.path.isfile(f) and not f.startswith('/dev'):
              srcdir = Path(self.environment.get_source_dir())
              builddir = Path(self.environment.get_build_dir())
              f = Path(f).resolve()
              if builddir in f.parents:
                  return
              if srcdir in f.parents:
                  f = f.relative_to(srcdir)
              f = str(f)
          else:
              return
          if f not in self.build_def_files:
              self.build_def_files.append(f)
  
      def get_variables(self):
          return self.variables
  
      def check_stdlibs(self):
          for for_machine in MachineChoice:
              props = self.build.environment.properties[for_machine]
              for l in self.coredata.compilers[for_machine].keys():
                  try:
                      di = mesonlib.stringlistify(props.get_stdlib(l))
                      if len(di) != 2:
                          raise InterpreterException('Stdlib definition for %s should have exactly two elements.'
                                                     % l)
                      projname, depname = di
                      subproj = self.do_subproject(projname, 'meson', {})
                      self.build.stdlibs.host[l] = subproj.get_variable_method([depname], {})
                  except KeyError:
                      pass
                  except InvalidArguments:
                      pass
  
      @stringArgs
      @noKwargs
      def func_import(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Import takes one argument.')
          modname = args[0]
          if modname.startswith('unstable-'):
              plainname = modname.split('-', 1)[1]
              mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node)
              modname = 'unstable_' + plainname
          if modname not in self.modules:
              try:
                  module = importlib.import_module('mesonbuild.modules.' + modname)
              except ImportError:
                  raise InvalidArguments('Module "%s" does not exist' % (modname, ))
              self.modules[modname] = module.initialize(self)
          return ModuleHolder(modname, self.modules[modname], self)
  
      @stringArgs
      @noKwargs
      def func_files(self, node, args, kwargs):
          return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args]
  
      @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole'])
      @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables'])
      @permittedKwargs(permitted_kwargs['declare_dependency'])
      @noPosargs
      def func_declare_dependency(self, node, args, kwargs):
          version = kwargs.get('version', self.project_version)
          if not isinstance(version, str):
              raise InterpreterException('Version must be a string.')
          incs = self.extract_incdirs(kwargs)
          libs = unholder(extract_as_list(kwargs, 'link_with'))
          libs_whole = unholder(extract_as_list(kwargs, 'link_whole'))
          sources = extract_as_list(kwargs, 'sources')
          sources = unholder(listify(self.source_strings_to_files(sources)))
          deps = unholder(extract_as_list(kwargs, 'dependencies'))
          compile_args = mesonlib.stringlistify(kwargs.get('compile_args', []))
          link_args = mesonlib.stringlistify(kwargs.get('link_args', []))
          variables = kwargs.get('variables', {})
          if not isinstance(variables, dict):
              raise InterpreterException('variables must be a dict.')
          if not all(isinstance(v, str) for v in variables.values()):
              # Because that is how they will come from pkg-config and cmake
              raise InterpreterException('variables values be strings.')
          final_deps = []
          for d in deps:
              try:
                  d = d.held_object
              except Exception:
                  pass
              if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)):
                  raise InterpreterException('Dependencies must be external deps')
              final_deps.append(d)
          for l in libs:
              if isinstance(l, dependencies.Dependency):
                  raise InterpreterException('''Entries in "link_with" may only be self-built targets,
  external dependencies (including libraries) must go to "dependencies".''')
          dep = dependencies.InternalDependency(version, incs, compile_args,
                                                link_args, libs, libs_whole, sources, final_deps,
                                                variables)
          return DependencyHolder(dep, self.subproject)
  
      @noKwargs
      def func_assert(self, node, args, kwargs):
          if len(args) == 1:
              FeatureNew('assert function without message argument', '0.53.0').use(self.subproject)
              value = args[0]
              message = None
          elif len(args) == 2:
              value, message = args
              if not isinstance(message, str):
                  raise InterpreterException('Assert message not a string.')
          else:
              raise InterpreterException('Assert takes between one and two arguments')
          if not isinstance(value, bool):
              raise InterpreterException('Assert value not bool.')
          if not value:
              if message is None:
                  from .ast import AstPrinter
                  printer = AstPrinter()
                  node.args.arguments[0].accept(printer)
                  message = printer.result
              raise InterpreterException('Assert failed: ' + message)
  
      def validate_arguments(self, args, argcount, arg_types):
          if argcount is not None:
              if argcount != len(args):
                  raise InvalidArguments('Expected %d arguments, got %d.' %
                                         (argcount, len(args)))
          for actual, wanted in zip(args, arg_types):
              if wanted is not None:
                  if not isinstance(actual, wanted):
                      raise InvalidArguments('Incorrect argument type.')
  
      @FeatureNewKwargs('run_command', '0.50.0', ['env'])
      @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture'])
      @permittedKwargs(permitted_kwargs['run_command'])
      def func_run_command(self, node, args, kwargs):
          return self.run_command_impl(node, args, kwargs)
  
      def run_command_impl(self, node, args, kwargs, in_builddir=False):
          if len(args) < 1:
              raise InterpreterException('Not enough arguments')
          cmd, *cargs = args
          capture = kwargs.get('capture', True)
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
  
          check = kwargs.get('check', False)
          if not isinstance(check, bool):
              raise InterpreterException('Check must be boolean.')
  
          env = self.unpack_env_kwarg(kwargs)
  
          m = 'must be a string, or the output of find_program(), files() '\
              'or configure_file(), or a compiler object; not {!r}'
          expanded_args = []
          if isinstance(cmd, ExternalProgramHolder):
              cmd = cmd.held_object
              if isinstance(cmd, build.Executable):
                  progname = node.args.arguments[0].value
                  msg = 'Program {!r} was overridden with the compiled executable {!r}'\
                        ' and therefore cannot be used during configuration'
                  raise InterpreterException(msg.format(progname, cmd.description()))
              if not cmd.found():
                  raise InterpreterException('command {!r} not found or not executable'.format(cmd.get_name()))
          elif isinstance(cmd, CompilerHolder):
              exelist = cmd.compiler.get_exelist()
              cmd = exelist[0]
              prog = ExternalProgram(cmd, silent=True)
              if not prog.found():
                  raise InterpreterException('Program {!r} not found '
                                             'or not executable'.format(cmd))
              cmd = prog
              expanded_args = exelist[1:]
          else:
              if isinstance(cmd, mesonlib.File):
                  cmd = cmd.absolute_path(srcdir, builddir)
              elif not isinstance(cmd, str):
                  raise InterpreterException('First argument ' + m.format(cmd))
              # Prefer scripts in the current source directory
              search_dir = os.path.join(srcdir, self.subdir)
              prog = ExternalProgram(cmd, silent=True, search_dir=search_dir)
              if not prog.found():
                  raise InterpreterException('Program or command {!r} not found '
                                             'or not executable'.format(cmd))
              cmd = prog
          for a in listify(cargs):
              if isinstance(a, str):
                  expanded_args.append(a)
              elif isinstance(a, mesonlib.File):
                  expanded_args.append(a.absolute_path(srcdir, builddir))
              elif isinstance(a, ExternalProgramHolder):
                  expanded_args.append(a.held_object.get_path())
              else:
                  raise InterpreterException('Arguments ' + m.format(a))
          # If any file that was used as an argument to the command
          # changes, we must re-run the configuration step.
          self.add_build_def_file(cmd.get_path())
          for a in expanded_args:
              if not os.path.isabs(a):
                  a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a)
              self.add_build_def_file(a)
          return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir,
                            self.environment.get_build_command() + ['introspect'],
                            in_builddir=in_builddir, check=check, capture=capture)
  
      @stringArgs
      def func_gettext(self, nodes, args, kwargs):
          raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead')
  
      def func_option(self, nodes, args, kwargs):
          raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.')
  
      @FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
      @permittedKwargs(permitted_kwargs['subproject'])
      @stringArgs
      def func_subproject(self, nodes, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Subproject takes exactly one argument')
          dirname = args[0]
          return self.do_subproject(dirname, 'meson', kwargs)
  
      def disabled_subproject(self, dirname, disabled_feature=None, exception=None):
          sub = SubprojectHolder(None, self.subproject_dir, dirname,
                                 disabled_feature=disabled_feature, exception=exception)
          self.subprojects[dirname] = sub
          return sub
  
      def do_subproject(self, dirname: str, method: str, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.disabled_subproject(dirname, disabled_feature=feature)
  
          default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
          default_options = coredata.create_options_dict(default_options)
          if dirname == '':
              raise InterpreterException('Subproject dir name must not be empty.')
          if dirname[0] == '.':
              raise InterpreterException('Subproject dir name must not start with a period.')
          if '..' in dirname:
              raise InterpreterException('Subproject name must not contain a ".." path segment.')
          if os.path.isabs(dirname):
              raise InterpreterException('Subproject name must not be an absolute path.')
          if has_path_sep(dirname):
              mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.',
                           location=self.current_node)
          if dirname in self.subproject_stack:
              fullstack = self.subproject_stack + [dirname]
              incpath = ' => '.join(fullstack)
              raise InvalidCode('Recursive include of subprojects: %s.' % incpath)
          if dirname in self.subprojects:
              subproject = self.subprojects[dirname]
              if required and not subproject.found():
                  raise InterpreterException('Subproject "%s/%s" required but not found.' % (
                                             self.subproject_dir, dirname))
              return subproject
  
          subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
          r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode'))
          try:
              resolved = r.resolve(dirname, method)
          except wrap.WrapException as e:
              subprojdir = os.path.join(self.subproject_dir, r.directory)
              if isinstance(e, wrap.WrapNotFoundException):
                  # if the reason subproject execution failed was because
                  # the directory doesn't exist, try to give some helpful
                  # advice if it's a nested subproject that needs
                  # promotion...
                  self.print_nested_info(dirname)
              if not required:
                  mlog.log(e)
                  mlog.log('Subproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)')
                  return self.disabled_subproject(dirname, exception=e)
              raise e
  
          subdir = os.path.join(self.subproject_dir, resolved)
          subdir_abs = os.path.join(subproject_dir_abs, resolved)
          os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
          self.global_args_frozen = True
  
          mlog.log()
          with mlog.nested():
              mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n')
          try:
              if method == 'meson':
                  return self._do_subproject_meson(dirname, subdir, default_options, kwargs)
              elif method == 'cmake':
                  return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, kwargs)
              else:
                  raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname))
          # Invalid code is always an error
          except InvalidCode:
              raise
          except Exception as e:
              if not required:
                  with mlog.nested():
                      # Suppress the 'ERROR:' prefix because this exception is not
                      # fatal and VS CI treat any logs with "ERROR:" as fatal.
                      mlog.exception(e, prefix=mlog.yellow('Exception:'))
                  mlog.log('\nSubproject', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)')
                  return self.disabled_subproject(dirname, exception=e)
              raise e
  
      def _do_subproject_meson(self, dirname, subdir, default_options, kwargs, ast=None, build_def_files=None):
          with mlog.nested():
              new_build = self.build.copy()
              subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
                                 self.modules, default_options, ast=ast)
              subi.subprojects = self.subprojects
  
              subi.subproject_stack = self.subproject_stack + [dirname]
              current_active = self.active_projectname
              current_warnings_counter = mlog.log_warnings_counter
              mlog.log_warnings_counter = 0
              subi.run()
              subi_warnings = mlog.log_warnings_counter
              mlog.log_warnings_counter = current_warnings_counter
  
              mlog.log('Subproject', mlog.bold(dirname), 'finished.')
  
          mlog.log()
  
          if 'version' in kwargs:
              pv = subi.project_version
              wanted = kwargs['version']
              if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
                  raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted))
          self.active_projectname = current_active
          self.subprojects.update(subi.subprojects)
          self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname,
                                                       warnings=subi_warnings)
          # Duplicates are possible when subproject uses files from project root
          if build_def_files:
              self.build_def_files = list(set(self.build_def_files + build_def_files))
          else:
              self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
          self.build.merge(subi.build)
          self.build.subprojects[dirname] = subi.project_version
          self.summary.update(subi.summary)
          return self.subprojects[dirname]
  
      def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwargs):
          with mlog.nested():
              new_build = self.build.copy()
              prefix = self.coredata.builtins['prefix'].value
              cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
              cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend)
              cm_int.initialise(cmake_options)
              cm_int.analyse()
  
              # Generate a meson ast and execute it with the normal do_subproject_meson
              ast = cm_int.pretend_to_be_meson()
  
              mlog.log()
              with mlog.nested():
                  mlog.log('Processing generated meson AST')
  
                  # Debug print the generated meson file
                  from .ast import AstIndentationGenerator, AstPrinter
                  printer = AstPrinter()
                  ast.accept(AstIndentationGenerator())
                  ast.accept(printer)
                  printer.post_process()
                  meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build')
                  with open(meson_filename, "w") as f:
                      f.write(printer.result)
  
                  mlog.log('Build file:', meson_filename)
                  mlog.cmd_ci_include(meson_filename)
                  mlog.log()
  
              result = self._do_subproject_meson(dirname, subdir, default_options, kwargs, ast, cm_int.bs_files)
              result.cm_interpreter = cm_int
  
          mlog.log()
          return result
  
      def get_option_internal(self, optname):
          raw_optname = optname
          if self.is_subproject():
              optname = self.subproject + ':' + optname
  
          for opts in [
                  self.coredata.base_options, compilers.base_options, self.coredata.builtins,
                  dict(self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine)),
                  dict(self.coredata.flatten_lang_iterator(
                      self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options))),
          ]:
              v = opts.get(optname)
              if v is None or v.yielding:
                  v = opts.get(raw_optname)
              if v is not None:
                  return v
  
          try:
              opt = self.coredata.user_options[optname]
              if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options:
                  popt = self.coredata.user_options[raw_optname]
                  if type(opt) is type(popt):
                      opt = popt
                  else:
                      # Get class name, then option type as a string
                      opt_type = opt.__class__.__name__[4:][:-6].lower()
                      popt_type = popt.__class__.__name__[4:][:-6].lower()
                      # This is not a hard error to avoid dependency hell, the workaround
                      # when this happens is to simply set the subproject's option directly.
                      mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield '
                                   'to parent option of type {3!r}, ignoring parent value. '
                                   'Use -D{2}:{0}=value to set the value for this option manually'
                                   '.'.format(raw_optname, opt_type, self.subproject, popt_type),
                                   location=self.current_node)
              return opt
          except KeyError:
              pass
  
          raise InterpreterException('Tried to access unknown option "%s".' % optname)
  
      @stringArgs
      @noKwargs
      def func_get_option(self, nodes, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Argument required for get_option.')
          optname = args[0]
          if ':' in optname:
              raise InterpreterException('Having a colon in option name is forbidden, '
                                         'projects are not allowed to directly access '
                                         'options of other subprojects.')
          opt = self.get_option_internal(optname)
          if isinstance(opt, coredata.UserFeatureOption):
              return FeatureOptionHolder(self.environment, optname, opt)
          elif isinstance(opt, coredata.UserOption):
              return opt.value
          return opt
  
      @noKwargs
      def func_configuration_data(self, node, args, kwargs):
          if len(args) > 1:
              raise InterpreterException('configuration_data takes only one optional positional arguments')
          elif len(args) == 1:
              FeatureNew('configuration_data dictionary', '0.49.0').use(self.subproject)
              initial_values = args[0]
              if not isinstance(initial_values, dict):
                  raise InterpreterException('configuration_data first argument must be a dictionary')
          else:
              initial_values = {}
          return ConfigurationDataHolder(self.subproject, initial_values)
  
      def set_backend(self):
          # The backend is already set when parsing subprojects
          if self.backend is not None:
              return
          backend = self.coredata.get_builtin_option('backend')
          from .backend import backends
          self.backend = backends.get_backend_from_name(backend, self.build, self)
  
          if self.backend is None:
              raise InterpreterException('Unknown backend "%s".' % backend)
          if backend != self.backend.name:
              if self.backend.name.startswith('vs'):
                  mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
              self.coredata.set_builtin_option('backend', self.backend.name)
  
          # Only init backend options on first invocation otherwise it would
          # override values previously set from command line.
          if self.environment.first_invocation:
              self.coredata.init_backend_options(backend)
  
          options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')}
          self.coredata.set_options(options)
  
      @stringArgs
      @permittedKwargs(permitted_kwargs['project'])
      def func_project(self, node, args, kwargs):
          if len(args) < 1:
              raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
          proj_name, *proj_langs = args
          if ':' in proj_name:
              raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name))
  
          if 'meson_version' in kwargs:
              cv = coredata.version
              pv = kwargs['meson_version']
              if not mesonlib.version_compare(cv, pv):
                  raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv))
  
          if os.path.exists(self.option_file):
              oi = optinterpreter.OptionInterpreter(self.subproject)
              oi.process(self.option_file)
              self.coredata.merge_user_options(oi.options)
              self.add_build_def_file(self.option_file)
  
          # Do not set default_options on reconfigure otherwise it would override
          # values previously set from command line. That means that changing
          # default_options in a project will trigger a reconfigure but won't
          # have any effect.
          self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
          self.project_default_options = coredata.create_options_dict(self.project_default_options)
          if self.environment.first_invocation:
              default_options = self.project_default_options
              default_options.update(self.default_project_options)
              self.coredata.init_builtins(self.subproject)
          else:
              default_options = {}
          self.coredata.set_default_options(default_options, self.subproject, self.environment)
  
          if not self.is_subproject():
              self.build.project_name = proj_name
          self.active_projectname = proj_name
          self.project_version = kwargs.get('version', 'undefined')
          if self.build.project_version is None:
              self.build.project_version = self.project_version
          proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown'))
          self.build.dep_manifest[proj_name] = {'version': self.project_version,
                                                'license': proj_license}
          if self.subproject in self.build.projects:
              raise InvalidCode('Second call to project().')
          if not self.is_subproject() and 'subproject_dir' in kwargs:
              spdirname = kwargs['subproject_dir']
              if not isinstance(spdirname, str):
                  raise InterpreterException('Subproject_dir must be a string')
              if os.path.isabs(spdirname):
                  raise InterpreterException('Subproject_dir must not be an absolute path.')
              if spdirname.startswith('.'):
                  raise InterpreterException('Subproject_dir must not begin with a period.')
              if '..' in spdirname:
                  raise InterpreterException('Subproject_dir must not contain a ".." segment.')
              self.subproject_dir = spdirname
  
          self.build.subproject_dir = self.subproject_dir
  
          mesonlib.project_meson_versions[self.subproject] = ''
          if 'meson_version' in kwargs:
              mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
  
          self.build.projects[self.subproject] = proj_name
          mlog.log('Project name:', mlog.bold(proj_name))
          mlog.log('Project version:', mlog.bold(self.project_version))
          self.add_languages(proj_langs, True, MachineChoice.BUILD)
          self.add_languages(proj_langs, True, MachineChoice.HOST)
          self.set_backend()
          if not self.is_subproject():
              self.check_stdlibs()
  
      @FeatureNewKwargs('add_languages', '0.54.0', ['native'])
      @permittedKwargs(permitted_kwargs['add_languages'])
      @stringArgs
      def func_add_languages(self, node, args, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              for lang in sorted(args, key=compilers.sort_clink):
                  mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          if 'native' in kwargs:
              return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs))
          else:
              # absent 'native' means 'both' for backwards compatibility
              mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.',
                           location=self.current_node)
              success = self.add_languages(args, False, MachineChoice.BUILD)
              success &= self.add_languages(args, required, MachineChoice.HOST)
              return success
  
      def get_message_string_arg(self, arg):
          if isinstance(arg, list):
              argstr = stringifyUserArguments(arg)
          elif isinstance(arg, dict):
              argstr = stringifyUserArguments(arg)
          elif isinstance(arg, str):
              argstr = arg
          elif isinstance(arg, int):
              argstr = str(arg)
          else:
              raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
  
          return argstr
  
      @noArgsFlattening
      @noKwargs
      def func_message(self, node, args, kwargs):
          if len(args) > 1:
              FeatureNew('message with more than one argument', '0.54.0').use(self.subproject)
          args_str = [self.get_message_string_arg(i) for i in args]
          self.message_impl(args_str)
  
      def message_impl(self, args):
          mlog.log(mlog.bold('Message:'), *args)
  
      @noArgsFlattening
      @FeatureNewKwargs('summary', '0.54.0', ['list_sep'])
      @permittedKwargs({'section', 'bool_yn', 'list_sep'})
      @FeatureNew('summary', '0.53.0')
      def func_summary(self, node, args, kwargs):
          if len(args) == 1:
              if not isinstance(args[0], dict):
                  raise InterpreterException('Summary first argument must be dictionary.')
              values = args[0]
          elif len(args) == 2:
              if not isinstance(args[0], str):
                  raise InterpreterException('Summary first argument must be string.')
              values = {args[0]: args[1]}
          else:
              raise InterpreterException('Summary accepts at most 2 arguments.')
          section = kwargs.get('section', '')
          if not isinstance(section, str):
              raise InterpreterException('Summary\'s section keyword argument must be string.')
          self.summary_impl(section, values, kwargs)
  
      def summary_impl(self, section, values, kwargs):
          if self.subproject not in self.summary:
              self.summary[self.subproject] = Summary(self.active_projectname, self.project_version)
          self.summary[self.subproject].add_section(section, values, kwargs)
  
      def _print_summary(self):
          # Add automatic 'Supbrojects' section in main project.
          all_subprojects = collections.OrderedDict()
          for name, subp in sorted(self.subprojects.items()):
              value = subp.found()
              if subp.disabled_feature:
                  value = [value, 'Feature {!r} disabled'.format(subp.disabled_feature)]
              elif subp.exception:
                  value = [value, str(subp.exception)]
              elif subp.warnings > 0:
                  value = [value, '{} warnings'.format(subp.warnings)]
              all_subprojects[name] = value
          if all_subprojects:
              self.summary_impl('Subprojects', all_subprojects,
                                {'bool_yn': True,
                                 'list_sep': ' ',
                                })
          # Print all summaries, main project last.
          mlog.log('')  # newline
          main_summary = self.summary.pop('', None)
          for _, summary in sorted(self.summary.items()):
              summary.dump()
          if main_summary:
              main_summary.dump()
  
      @noArgsFlattening
      @FeatureNew('warning', '0.44.0')
      @noKwargs
      def func_warning(self, node, args, kwargs):
          if len(args) > 1:
              FeatureNew('warning with more than one argument', '0.54.0').use(self.subproject)
          args_str = [self.get_message_string_arg(i) for i in args]
          mlog.warning(*args_str, location=node)
  
      @noKwargs
      def func_error(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          raise InterpreterException('Problem encountered: ' + args[0])
  
      @noKwargs
      def func_exception(self, node, args, kwargs):
          self.validate_arguments(args, 0, [])
          raise Exception()
  
      def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool:
          success = self.add_languages_for(args, required, for_machine)
          if not self.coredata.is_cross_build():
              self.coredata.copy_build_options_from_regular_ones()
          self._redetect_machines()
          return success
  
      def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool:
          if for_machine != MachineChoice.HOST:
              return False
          if not self.environment.is_cross_build():
              return False
          should = self.environment.properties.host.get('skip_sanity_check', False)
          if not isinstance(should, bool):
              raise InterpreterException('Option skip_sanity_check must be a boolean.')
          return should
  
      def add_languages_for(self, args, required, for_machine: MachineChoice):
          success = True
          for lang in sorted(args, key=compilers.sort_clink):
              lang = lang.lower()
              clist = self.coredata.compilers[for_machine]
              machine_name = for_machine.get_lower_case_name()
              if lang in clist:
                  comp = clist[lang]
              else:
                  try:
                      comp = self.environment.detect_compiler_for(lang, for_machine)
                      if comp is None:
                          raise InvalidArguments('Tried to use unknown language "%s".' % lang)
                      if self.should_skip_sanity_check(for_machine):
                          mlog.log_once('Cross compiler sanity tests disabled via the cross file.')
                      else:
                          comp.sanity_check(self.environment.get_scratch_dir(), self.environment)
                  except Exception:
                      if not required:
                          mlog.log('Compiler for language',
                                   mlog.bold(lang), 'for the', machine_name,
                                   'machine not found.')
                          success = False
                          continue
                      else:
                          raise
  
              if for_machine == MachineChoice.HOST or self.environment.is_cross_build():
                  logger_fun = mlog.log
              else:
                  logger_fun = mlog.debug
              logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:',
                         mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string())
              if comp.linker is not None:
                  logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:',
                             mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version)
              self.build.ensure_static_linker(comp)
  
          langs = self.coredata.compilers[for_machine].keys()
          if 'vala' in langs:
              if 'c' not in langs:
                  raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.')
  
          return success
  
      def program_from_file_for(self, for_machine, prognames, silent):
          for p in unholder(prognames):
              if isinstance(p, mesonlib.File):
                  continue # Always points to a local (i.e. self generated) file.
              if not isinstance(p, str):
                  raise InterpreterException('Executable name must be a string')
              prog = ExternalProgram.from_bin_list(self.environment, for_machine, p)
              if prog.found():
                  return ExternalProgramHolder(prog, self.subproject)
          return None
  
      def program_from_system(self, args, search_dirs, silent=False):
          # Search for scripts relative to current subdir.
          # Do not cache found programs because find_program('foobar')
          # might give different results when run from different source dirs.
          source_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
          for exename in args:
              if isinstance(exename, mesonlib.File):
                  if exename.is_built:
                      search_dir = os.path.join(self.environment.get_build_dir(),
                                                exename.subdir)
                  else:
                      search_dir = os.path.join(self.environment.get_source_dir(),
                                                exename.subdir)
                  exename = exename.fname
                  extra_search_dirs = []
              elif isinstance(exename, str):
                  search_dir = source_dir
                  extra_search_dirs = search_dirs
              else:
                  raise InvalidArguments('find_program only accepts strings and '
                                         'files, not {!r}'.format(exename))
              extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
                                                     extra_search_dirs=extra_search_dirs,
                                                     silent=silent)
              progobj = ExternalProgramHolder(extprog, self.subproject)
              if progobj.found():
                  return progobj
  
      def program_from_overrides(self, command_names, silent=False):
          for name in command_names:
              if not isinstance(name, str):
                  continue
              if name in self.build.find_overrides:
                  exe = self.build.find_overrides[name]
                  if not silent:
                      mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
                               '(overridden: %s)' % exe.description())
                  return ExternalProgramHolder(exe, self.subproject, self.backend)
          return None
  
      def store_name_lookups(self, command_names):
          for name in command_names:
              if isinstance(name, str):
                  self.build.searched_programs.add(name)
  
      def add_find_program_override(self, name, exe):
          if name in self.build.searched_programs:
              raise InterpreterException('Tried to override finding of executable "%s" which has already been found.'
                                         % name)
          if name in self.build.find_overrides:
              raise InterpreterException('Tried to override executable "%s" which has already been overridden.'
                                         % name)
          self.build.find_overrides[name] = exe
  
      # TODO update modules to always pass `for_machine`. It is bad-form to assume
      # the host machine.
      def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST,
                            required=True, silent=True, wanted='', search_dirs=None):
          if not isinstance(args, list):
              args = [args]
  
          progobj = self.program_from_overrides(args, silent=silent)
          if progobj is None:
              progobj = self.program_from_file_for(for_machine, args, silent=silent)
          if progobj is None:
              progobj = self.program_from_system(args, search_dirs, silent=silent)
          if progobj is None and args[0].endswith('python3'):
              prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True)
              progobj = ExternalProgramHolder(prog, self.subproject)
          if required and (progobj is None or not progobj.found()):
              raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args))
          if progobj is None:
              return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
          # Only store successful lookups
          self.store_name_lookups(args)
          if wanted:
              version = progobj.get_version(self)
              is_found, not_found, found = mesonlib.version_compare_many(version, wanted)
              if not is_found:
                  mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'),
                           'found {!r} but need:'.format(version),
                           ', '.join(["'{}'".format(e) for e in not_found]))
                  if required:
                      m = 'Invalid version of program, need {!r} {!r} found {!r}.'
                      raise InvalidArguments(m.format(progobj.get_name(), not_found, version))
                  return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
          return progobj
  
      @FeatureNewKwargs('find_program', '0.53.0', ['dirs'])
      @FeatureNewKwargs('find_program', '0.52.0', ['version'])
      @FeatureNewKwargs('find_program', '0.49.0', ['disabler'])
      @disablerIfNotFound
      @permittedKwargs(permitted_kwargs['find_program'])
      def func_find_program(self, node, args, kwargs):
          if not args:
              raise InterpreterException('No program name specified.')
  
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled')
              return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
  
          search_dirs = extract_search_dirs(kwargs)
          wanted = mesonlib.stringlistify(kwargs.get('version', []))
          for_machine = self.machine_from_native_kwarg(kwargs)
          return self.find_program_impl(args, for_machine, required=required,
                                        silent=False, wanted=wanted,
                                        search_dirs=search_dirs)
  
      def func_find_library(self, node, args, kwargs):
          raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
                            'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n'
                            'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
                            )
  
      def _find_cached_dep(self, name, display_name, kwargs):
          # Check if we want this as a build-time / build machine or runt-time /
          # host machine dep.
          for_machine = self.machine_from_native_kwarg(kwargs)
          identifier = dependencies.get_dep_identifier(name, kwargs)
          wanted_vers = mesonlib.stringlistify(kwargs.get('version', []))
  
          override = self.build.dependency_overrides[for_machine].get(identifier)
          if override:
              info = [mlog.blue('(overridden)' if override.explicit else '(cached)')]
              cached_dep = override.dep
              # We don't implicitly override not-found dependencies, but user could
              # have explicitly called meson.override_dependency() with a not-found
              # dep.
              if not cached_dep.found():
                  mlog.log('Dependency', mlog.bold(display_name),
                           'found:', mlog.red('NO'), *info)
                  return identifier, cached_dep
              found_vers = cached_dep.get_version()
              if not self.check_version(wanted_vers, found_vers):
                  mlog.log('Dependency', mlog.bold(name),
                           'found:', mlog.red('NO'),
                           'found', mlog.normal_cyan(found_vers), 'but need:',
                           mlog.bold(', '.join(["'{}'".format(e) for e in wanted_vers])),
                           *info)
                  return identifier, NotFoundDependency(self.environment)
          else:
              info = [mlog.blue('(cached)')]
              cached_dep = self.coredata.deps[for_machine].get(identifier)
              if cached_dep:
                  found_vers = cached_dep.get_version()
                  if not self.check_version(wanted_vers, found_vers):
                      return identifier, None
  
          if cached_dep:
              if found_vers:
                  info = [mlog.normal_cyan(found_vers), *info]
              mlog.log('Dependency', mlog.bold(display_name),
                       'found:', mlog.green('YES'), *info)
              return identifier, cached_dep
  
          return identifier, None
  
      @staticmethod
      def check_version(wanted, found):
          if not wanted:
              return True
          if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]:
              return False
          return True
  
      def notfound_dependency(self):
          return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
  
      def verify_fallback_consistency(self, dirname, varname, cached_dep):
          subi = self.subprojects.get(dirname)
          if not cached_dep or not varname or not subi or not cached_dep.found():
              return
          dep = subi.get_variable_method([varname], {})
          if dep.held_object != cached_dep:
              m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}'
              raise DependencyException(m.format(varname))
  
      def get_subproject_dep(self, name, display_name, dirname, varname, kwargs):
          required = kwargs.get('required', True)
          wanted = mesonlib.stringlistify(kwargs.get('version', []))
          subproj_path = os.path.join(self.subproject_dir, dirname)
          dep = self.notfound_dependency()
          try:
              subproject = self.subprojects[dirname]
              _, cached_dep = self._find_cached_dep(name, display_name, kwargs)
              if varname is None:
                  # Assuming the subproject overridden the dependency we want
                  if cached_dep:
                      if required and not cached_dep.found():
                          m = 'Dependency {!r} is not satisfied'
                          raise DependencyException(m.format(display_name))
                      return DependencyHolder(cached_dep, self.subproject)
                  else:
                      m = 'Subproject {} did not override dependency {}'
                      raise DependencyException(m.format(subproj_path, display_name))
              if subproject.found():
                  self.verify_fallback_consistency(dirname, varname, cached_dep)
                  dep = self.subprojects[dirname].get_variable_method([varname], {})
          except InvalidArguments:
              pass
  
          if not isinstance(dep, DependencyHolder):
              raise InvalidCode('Fetched variable {!r} in the subproject {!r} is '
                                'not a dependency object.'.format(varname, dirname))
  
          if not dep.found():
              if required:
                  raise DependencyException('Could not find dependency {} in subproject {}'
                                            ''.format(varname, dirname))
              # If the dependency is not required, don't raise an exception
              mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                       mlog.bold(subproj_path), 'found:', mlog.red('NO'))
              return dep
  
          found = dep.held_object.get_version()
          if not self.check_version(wanted, found):
              if required:
                  raise DependencyException('Version {} of subproject dependency {} already '
                                            'cached, requested incompatible version {} for '
                                            'dep {}'.format(found, dirname, wanted, display_name))
  
              mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                       mlog.bold(subproj_path), 'found:', mlog.red('NO'),
                       'found', mlog.normal_cyan(found), 'but need:',
                       mlog.bold(', '.join(["'{}'".format(e) for e in wanted])))
              return self.notfound_dependency()
  
          found = mlog.normal_cyan(found) if found else None
          mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                   mlog.bold(subproj_path), 'found:', mlog.green('YES'), found)
          return dep
  
      def _handle_featurenew_dependencies(self, name):
          'Do a feature check on dependencies used by this subproject'
          if name == 'mpi':
              FeatureNew('MPI Dependency', '0.42.0').use(self.subproject)
          elif name == 'pcap':
              FeatureNew('Pcap Dependency', '0.42.0').use(self.subproject)
          elif name == 'vulkan':
              FeatureNew('Vulkan Dependency', '0.42.0').use(self.subproject)
          elif name == 'libwmf':
              FeatureNew('LibWMF Dependency', '0.44.0').use(self.subproject)
          elif name == 'openmp':
              FeatureNew('OpenMP Dependency', '0.46.0').use(self.subproject)
  
      @FeatureNewKwargs('dependency', '0.54.0', ['components'])
      @FeatureNewKwargs('dependency', '0.52.0', ['include_type'])
      @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args'])
      @FeatureNewKwargs('dependency', '0.49.0', ['disabler'])
      @FeatureNewKwargs('dependency', '0.40.0', ['method'])
      @FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
      @disablerIfNotFound
      @permittedKwargs(permitted_kwargs['dependency'])
      def func_dependency(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          name = args[0]
          display_name = name if name else '(anonymous)'
          mods = extract_as_list(kwargs, 'modules')
          if mods:
              display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
          not_found_message = kwargs.get('not_found_message', '')
          if not isinstance(not_found_message, str):
              raise InvalidArguments('The not_found_message must be a string.')
          try:
              d = self.dependency_impl(name, display_name, kwargs)
          except Exception:
              if not_found_message:
                  self.message_impl([not_found_message])
              raise
          if not d.found() and not_found_message:
              self.message_impl([not_found_message])
              self.message_impl([not_found_message])
          # Override this dependency to have consistent results in subsequent
          # dependency lookups.
          if name and d.found():
              for_machine = self.machine_from_native_kwarg(kwargs)
              identifier = dependencies.get_dep_identifier(name, kwargs)
              if identifier not in self.build.dependency_overrides[for_machine]:
                  self.build.dependency_overrides[for_machine][identifier] = \
                      build.DependencyOverride(d.held_object, node, explicit=False)
          return d
  
      def dependency_impl(self, name, display_name, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.notfound_dependency()
  
          has_fallback = 'fallback' in kwargs
          if 'default_options' in kwargs and not has_fallback:
              mlog.warning('The "default_options" keyworg argument does nothing without a "fallback" keyword argument.',
                           location=self.current_node)
  
          # writing just "dependency('')" is an error, because it can only fail
          if name == '' and required and not has_fallback:
              raise InvalidArguments('Dependency is both required and not-found')
  
          if '<' in name or '>' in name or '=' in name:
              raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
                                     'version\n requirements use the \'version\' keyword argument instead.')
  
          identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs)
          if cached_dep:
              if has_fallback:
                  dirname, varname = self.get_subproject_infos(kwargs)
                  self.verify_fallback_consistency(dirname, varname, cached_dep)
              if required and not cached_dep.found():
                  m = 'Dependency {!r} was already checked and was not found'
                  raise DependencyException(m.format(display_name))
              return DependencyHolder(cached_dep, self.subproject)
  
          # If the dependency has already been configured, possibly by
          # a higher level project, try to use it first.
          if has_fallback:
              dirname, varname = self.get_subproject_infos(kwargs)
              if dirname in self.subprojects:
                  return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
  
          wrap_mode = self.coredata.get_builtin_option('wrap_mode')
          forcefallback = wrap_mode == WrapMode.forcefallback and has_fallback
          if name != '' and not forcefallback:
              self._handle_featurenew_dependencies(name)
              kwargs['required'] = required and not has_fallback
              dep = dependencies.find_external_dependency(name, self.environment, kwargs)
              kwargs['required'] = required
              # Only store found-deps in the cache
              # Never add fallback deps to self.coredata.deps since we
              # cannot cache them. They must always be evaluated else
              # we won't actually read all the build files.
              if dep.found():
                  for_machine = self.machine_from_native_kwarg(kwargs)
                  self.coredata.deps[for_machine].put(identifier, dep)
                  return DependencyHolder(dep, self.subproject)
  
          if has_fallback:
              return self.dependency_fallback(name, display_name, kwargs)
  
          return self.notfound_dependency()
  
      @FeatureNew('disabler', '0.44.0')
      @noKwargs
      @noPosargs
      def func_disabler(self, node, args, kwargs):
          return Disabler()
  
      def print_nested_info(self, dependency_name):
          message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' +
                     'To use it in the current project, promote it by going in the project source\n'
                     'root and issuing']
          sprojs = mesonlib.detect_subprojects('subprojects', self.source_root)
          if dependency_name not in sprojs:
              return
          found = sprojs[dependency_name]
          if len(found) > 1:
              message.append('one of the following commands:')
          else:
              message.append('the following command:')
          command_templ = '\nmeson wrap promote {}'
          for l in found:
              message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:])))
          mlog.warning(*message, location=self.current_node)
  
      def get_subproject_infos(self, kwargs):
          fbinfo = mesonlib.stringlistify(kwargs['fallback'])
          if len(fbinfo) == 1:
              FeatureNew('Fallback without variable name', '0.53.0').use(self.subproject)
              return fbinfo[0], None
          elif len(fbinfo) != 2:
              raise InterpreterException('Fallback info must have one or two items.')
          return fbinfo
  
      def dependency_fallback(self, name, display_name, kwargs):
          required = kwargs.get('required', True)
          if self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback:
              mlog.log('Not looking for a fallback subproject for the dependency',
                       mlog.bold(display_name), 'because:\nUse of fallback '
                       'dependencies is disabled.')
              if required:
                  m = 'Dependency {!r} not found and fallback is disabled'
                  raise DependencyException(m.format(display_name))
              return self.notfound_dependency()
          elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.forcefallback:
              mlog.log('Looking for a fallback subproject for the dependency',
                       mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.')
          else:
              mlog.log('Looking for a fallback subproject for the dependency',
                       mlog.bold(display_name))
          dirname, varname = self.get_subproject_infos(kwargs)
          sp_kwargs = {
              'default_options': kwargs.get('default_options', []),
              'required': required,
          }
          self.do_subproject(dirname, 'meson', sp_kwargs)
          return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
  
      @FeatureNewKwargs('executable', '0.42.0', ['implib'])
      @permittedKwargs(permitted_kwargs['executable'])
      def func_executable(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, ExecutableHolder)
  
      @permittedKwargs(permitted_kwargs['static_library'])
      def func_static_lib(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, StaticLibraryHolder)
  
      @permittedKwargs(permitted_kwargs['shared_library'])
      def func_shared_lib(self, node, args, kwargs):
          holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
          holder.held_object.shared_library_only = True
          return holder
  
      @permittedKwargs(permitted_kwargs['both_libraries'])
      def func_both_lib(self, node, args, kwargs):
          return self.build_both_libraries(node, args, kwargs)
  
      @FeatureNew('shared_module', '0.37.0')
      @permittedKwargs(permitted_kwargs['shared_module'])
      def func_shared_module(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, SharedModuleHolder)
  
      @permittedKwargs(permitted_kwargs['library'])
      def func_library(self, node, args, kwargs):
          return self.build_library(node, args, kwargs)
  
      @permittedKwargs(permitted_kwargs['jar'])
      def func_jar(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, JarHolder)
  
      @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
      @permittedKwargs(permitted_kwargs['build_target'])
      def func_build_target(self, node, args, kwargs):
          if 'target_type' not in kwargs:
              raise InterpreterException('Missing target_type keyword argument')
          target_type = kwargs.pop('target_type')
          if target_type == 'executable':
              return self.build_target(node, args, kwargs, ExecutableHolder)
          elif target_type == 'shared_library':
              return self.build_target(node, args, kwargs, SharedLibraryHolder)
          elif target_type == 'shared_module':
              FeatureNew('build_target(target_type: \'shared_module\')',
                         '0.51.0').use(self.subproject)
              return self.build_target(node, args, kwargs, SharedModuleHolder)
          elif target_type == 'static_library':
              return self.build_target(node, args, kwargs, StaticLibraryHolder)
          elif target_type == 'both_libraries':
              return self.build_both_libraries(node, args, kwargs)
          elif target_type == 'library':
              return self.build_library(node, args, kwargs)
          elif target_type == 'jar':
              return self.build_target(node, args, kwargs, JarHolder)
          else:
              raise InterpreterException('Unknown target_type.')
  
      @permittedKwargs(permitted_kwargs['vcs_tag'])
      def func_vcs_tag(self, node, args, kwargs):
          if 'input' not in kwargs or 'output' not in kwargs:
              raise InterpreterException('Keyword arguments input and output must exist')
          if 'fallback' not in kwargs:
              FeatureNew('Optional fallback in vcs_tag', '0.41.0').use(self.subproject)
          fallback = kwargs.pop('fallback', self.project_version)
          if not isinstance(fallback, str):
              raise InterpreterException('Keyword argument fallback must be a string.')
          replace_string = kwargs.pop('replace_string', '@VCS_TAG@')
          regex_selector = '(.*)' # default regex selector for custom command: use complete output
          vcs_cmd = kwargs.get('command', None)
          if vcs_cmd and not isinstance(vcs_cmd, list):
              vcs_cmd = [vcs_cmd]
          source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir))
          if vcs_cmd:
              # Is the command an executable in path or maybe a script in the source tree?
              vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0])
          else:
              vcs = mesonlib.detect_vcs(source_dir)
              if vcs:
                  mlog.log('Found %s repository at %s' % (vcs['name'], vcs['wc_dir']))
                  vcs_cmd = vcs['get_rev'].split()
                  regex_selector = vcs['rev_regex']
              else:
                  vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string
          # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command...
          kwargs['command'] = self.environment.get_build_command() + \
              ['--internal',
               'vcstagger',
               '@INPUT0@',
               '@OUTPUT0@',
               fallback,
               source_dir,
               replace_string,
               regex_selector] + vcs_cmd
          kwargs.setdefault('build_by_default', True)
          kwargs.setdefault('build_always_stale', True)
          return self._func_custom_target_impl(node, [kwargs['output']], kwargs)
  
      @FeatureNew('subdir_done', '0.46.0')
      @stringArgs
      def func_subdir_done(self, node, args, kwargs):
          if len(kwargs) > 0:
              raise InterpreterException('exit does not take named arguments')
          if len(args) > 0:
              raise InterpreterException('exit does not take any arguments')
          raise SubdirDoneRequest()
  
      @stringArgs
      @FeatureNewKwargs('custom_target', '0.48.0', ['console'])
      @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale'])
      @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default'])
      @permittedKwargs(permitted_kwargs['custom_target'])
      def func_custom_target(self, node, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
          if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
              FeatureNew('substitutions in custom_target depfile', '0.47.0').use(self.subproject)
          return self._func_custom_target_impl(node, args, kwargs)
  
      def _func_custom_target_impl(self, node, args, kwargs):
          'Implementation-only, without FeatureNew checks, for internal use'
          name = args[0]
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          if 'input' in kwargs:
              try:
                  kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
              except mesonlib.MesonException:
                  mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s).
  This will become a hard error in the future.''' % kwargs['input'], location=self.current_node)
          tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self)
          self.add_target(name, tg.held_object)
          return tg
  
      @permittedKwargs(permitted_kwargs['run_target'])
      def func_run_target(self, node, args, kwargs):
          if len(args) > 1:
              raise InvalidCode('Run_target takes only one positional argument: the target name.')
          elif len(args) == 1:
              if 'command' not in kwargs:
                  raise InterpreterException('Missing "command" keyword argument')
              all_args = extract_as_list(kwargs, 'command')
              deps = unholder(extract_as_list(kwargs, 'depends'))
          else:
              raise InterpreterException('Run_target needs at least one positional argument.')
  
          cleaned_args = []
          for i in unholder(listify(all_args)):
              if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)):
                  mlog.debug('Wrong type:', str(i))
                  raise InterpreterException('Invalid argument to run_target.')
              if isinstance(i, dependencies.ExternalProgram) and not i.found():
                  raise InterpreterException('Tried to use non-existing executable {!r}'.format(i.name))
              cleaned_args.append(i)
          name = args[0]
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string.')
          cleaned_deps = []
          for d in deps:
              if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
                  raise InterpreterException('Depends items must be build targets.')
              cleaned_deps.append(d)
          command, *cmd_args = cleaned_args
          tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self)
          self.add_target(name, tg.held_object)
          full_name = (self.subproject, name)
          assert(full_name not in self.build.run_target_names)
          self.build.run_target_names.add(full_name)
          return tg
  
      @FeatureNew('alias_target', '0.52.0')
      @noKwargs
      def func_alias_target(self, node, args, kwargs):
          if len(args) < 2:
              raise InvalidCode('alias_target takes at least 2 arguments.')
          name = args[0]
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string.')
          deps = unholder(listify(args[1:]))
          for d in deps:
              if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
                  raise InterpreterException('Depends items must be build targets.')
          tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self)
          self.add_target(name, tg.held_object)
          return tg
  
      @permittedKwargs(permitted_kwargs['generator'])
      def func_generator(self, node, args, kwargs):
          gen = GeneratorHolder(self, args, kwargs)
          self.generators.append(gen)
          return gen
  
      @FeatureNewKwargs('benchmark', '0.46.0', ['depends'])
      @FeatureNewKwargs('benchmark', '0.52.0', ['priority'])
      @permittedKwargs(permitted_kwargs['benchmark'])
      def func_benchmark(self, node, args, kwargs):
          # is_parallel isn't valid here, so make sure it isn't passed
          if 'is_parallel' in kwargs:
              del kwargs['is_parallel']
          self.add_test(node, args, kwargs, False)
  
      @FeatureNewKwargs('test', '0.46.0', ['depends'])
      @FeatureNewKwargs('test', '0.52.0', ['priority'])
      @permittedKwargs(permitted_kwargs['test'])
      def func_test(self, node, args, kwargs):
          self.add_test(node, args, kwargs, True)
  
      def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables:
          envlist = kwargs.get('env', EnvironmentVariablesHolder())
          if isinstance(envlist, EnvironmentVariablesHolder):
              env = envlist.held_object
          elif isinstance(envlist, dict):
              FeatureNew('environment dictionary', '0.52.0').use(self.subproject)
              env = EnvironmentVariablesHolder(envlist)
              env = env.held_object
          else:
              envlist = listify(envlist)
              # Convert from array to environment object
              env = EnvironmentVariablesHolder(envlist)
              env = env.held_object
          return env
  
      def add_test(self, node, args, kwargs, is_base_test):
          if len(args) != 2:
              raise InterpreterException('test expects 2 arguments, {} given'.format(len(args)))
          if not isinstance(args[0], str):
              raise InterpreterException('First argument of test must be a string.')
          exe = args[1]
          if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)):
              if isinstance(exe, mesonlib.File):
                  exe = self.func_find_program(node, args[1], {})
              else:
                  raise InterpreterException('Second argument must be executable.')
          par = kwargs.get('is_parallel', True)
          if not isinstance(par, bool):
              raise InterpreterException('Keyword argument is_parallel must be a boolean.')
          cmd_args = unholder(extract_as_list(kwargs, 'args'))
          for i in cmd_args:
              if not isinstance(i, (str, mesonlib.File, build.Target)):
                  raise InterpreterException('Command line arguments must be strings, files or targets.')
          env = self.unpack_env_kwarg(kwargs)
          should_fail = kwargs.get('should_fail', False)
          if not isinstance(should_fail, bool):
              raise InterpreterException('Keyword argument should_fail must be a boolean.')
          timeout = kwargs.get('timeout', 30)
          if 'workdir' in kwargs:
              workdir = kwargs['workdir']
              if not isinstance(workdir, str):
                  raise InterpreterException('Workdir keyword argument must be a string.')
              if not os.path.isabs(workdir):
                  raise InterpreterException('Workdir keyword argument must be an absolute path.')
          else:
              workdir = None
          if not isinstance(timeout, int):
              raise InterpreterException('Timeout must be an integer.')
          protocol = kwargs.get('protocol', 'exitcode')
          if protocol not in ('exitcode', 'tap'):
              raise InterpreterException('Protocol must be "exitcode" or "tap".')
          suite = []
          prj = self.subproject if self.is_subproject() else self.build.project_name
          for s in mesonlib.stringlistify(kwargs.get('suite', '')):
              if len(s) > 0:
                  s = ':' + s
              suite.append(prj.replace(' ', '_').replace(':', '_') + s)
          depends = unholder(extract_as_list(kwargs, 'depends'))
          for dep in depends:
              if not isinstance(dep, (build.CustomTarget, build.BuildTarget)):
                  raise InterpreterException('Depends items must be build targets.')
          priority = kwargs.get('priority', 0)
          if not isinstance(priority, int):
              raise InterpreterException('Keyword argument priority must be an integer.')
          t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args,
                   env, should_fail, timeout, workdir, protocol, priority)
          if is_base_test:
              self.build.tests.append(t)
              mlog.debug('Adding test', mlog.bold(args[0], True))
          else:
              self.build.benchmarks.append(t)
              mlog.debug('Adding benchmark', mlog.bold(args[0], True))
  
      @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_headers'])
      def func_install_headers(self, node, args, kwargs):
          source_files = self.source_strings_to_files(args)
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          h = Headers(source_files, kwargs)
          self.build.headers.append(h)
          return h
  
      @FeatureNewKwargs('install_man', '0.47.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_man'])
      def func_install_man(self, node, args, kwargs):
          fargs = self.source_strings_to_files(args)
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          m = Man(fargs, kwargs)
          self.build.man.append(m)
          return m
  
      @FeatureNewKwargs('subdir', '0.44.0', ['if_found'])
      @permittedKwargs(permitted_kwargs['subdir'])
      def func_subdir(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          mesonlib.check_direntry_issues(args)
          if '..' in args[0]:
              raise InvalidArguments('Subdir contains ..')
          if self.subdir == '' and args[0] == self.subproject_dir:
              raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.')
          if self.subdir == '' and args[0].startswith('meson-'):
              raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().')
          for i in mesonlib.extract_as_list(kwargs, 'if_found'):
              if not hasattr(i, 'found_method'):
                  raise InterpreterException('Object used in if_found does not have a found method.')
              if not i.found_method([], {}):
                  return
          prev_subdir = self.subdir
          subdir = os.path.join(prev_subdir, args[0])
          if os.path.isabs(subdir):
              raise InvalidArguments('Subdir argument must be a relative path.')
          absdir = os.path.join(self.environment.get_source_dir(), subdir)
          symlinkless_dir = os.path.realpath(absdir)
          if symlinkless_dir in self.visited_subdirs:
              raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'
                                     % subdir)
          self.visited_subdirs[symlinkless_dir] = True
          self.subdir = subdir
          os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True)
          buildfilename = os.path.join(self.subdir, environment.build_filename)
          self.build_def_files.append(buildfilename)
          absname = os.path.join(self.environment.get_source_dir(), buildfilename)
          if not os.path.isfile(absname):
              self.subdir = prev_subdir
              raise InterpreterException('Non-existent build file {!r}'.format(buildfilename))
          with open(absname, encoding='utf8') as f:
              code = f.read()
          assert(isinstance(code, str))
          try:
              codeblock = mparser.Parser(code, absname).parse()
          except mesonlib.MesonException as me:
              me.file = absname
              raise me
          try:
              self.evaluate_codeblock(codeblock)
          except SubdirDoneRequest:
              pass
          self.subdir = prev_subdir
  
      def _get_kwarg_install_mode(self, kwargs):
          if kwargs.get('install_mode', None) is None:
              return None
          install_mode = []
          mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
          for m in mode:
              # We skip any arguments that are set to `false`
              if m is False:
                  m = None
              install_mode.append(m)
          if len(install_mode) > 3:
              raise InvalidArguments('Keyword argument install_mode takes at '
                                     'most 3 arguments.')
          if len(install_mode) > 0 and install_mode[0] is not None and \
             not isinstance(install_mode[0], str):
              raise InvalidArguments('Keyword argument install_mode requires the '
                                     'permissions arg to be a string or false')
          return FileMode(*install_mode)
  
      @FeatureNewKwargs('install_data', '0.46.0', ['rename'])
      @FeatureNewKwargs('install_data', '0.38.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_data'])
      def func_install_data(self, node, args, kwargs):
          kwsource = mesonlib.stringlistify(kwargs.get('sources', []))
          raw_sources = args + kwsource
          sources = []
          source_strings = []
          for s in raw_sources:
              if isinstance(s, mesonlib.File):
                  sources.append(s)
              elif isinstance(s, str):
                  source_strings.append(s)
              else:
                  raise InvalidArguments('Argument {!r} must be string or file.'.format(s))
          sources += self.source_strings_to_files(source_strings)
          install_dir = kwargs.get('install_dir', None)
          if not isinstance(install_dir, (str, type(None))):
              raise InvalidArguments('Keyword argument install_dir not a string.')
          install_mode = self._get_kwarg_install_mode(kwargs)
          rename = kwargs.get('rename', None)
          data = DataHolder(build.Data(sources, install_dir, install_mode, rename))
          self.build.data.append(data.held_object)
          return data
  
      @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories'])
      @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_subdir'])
      @stringArgs
      def func_install_subdir(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidArguments('Install_subdir requires exactly one argument.')
          subdir = args[0]
          if 'install_dir' not in kwargs:
              raise InvalidArguments('Missing keyword argument install_dir')
          install_dir = kwargs['install_dir']
          if not isinstance(install_dir, str):
              raise InvalidArguments('Keyword argument install_dir not a string.')
          if 'strip_directory' in kwargs:
              if not isinstance(kwargs['strip_directory'], bool):
                  raise InterpreterException('"strip_directory" keyword must be a boolean.')
              strip_directory = kwargs['strip_directory']
          else:
              strip_directory = False
          if 'exclude_files' in kwargs:
              exclude = extract_as_list(kwargs, 'exclude_files')
              for f in exclude:
                  if not isinstance(f, str):
                      raise InvalidArguments('Exclude argument not a string.')
                  elif os.path.isabs(f):
                      raise InvalidArguments('Exclude argument cannot be absolute.')
              exclude_files = set(exclude)
          else:
              exclude_files = set()
          if 'exclude_directories' in kwargs:
              exclude = extract_as_list(kwargs, 'exclude_directories')
              for d in exclude:
                  if not isinstance(d, str):
                      raise InvalidArguments('Exclude argument not a string.')
                  elif os.path.isabs(d):
                      raise InvalidArguments('Exclude argument cannot be absolute.')
              exclude_directories = set(exclude)
          else:
              exclude_directories = set()
          exclude = (exclude_files, exclude_directories)
          install_mode = self._get_kwarg_install_mode(kwargs)
          idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
          self.build.install_dirs.append(idir)
          return idir
  
      @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding'])
      @FeatureNewKwargs('configure_file', '0.46.0', ['format'])
      @FeatureNewKwargs('configure_file', '0.41.0', ['capture'])
      @FeatureNewKwargs('configure_file', '0.50.0', ['install'])
      @FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
      @permittedKwargs(permitted_kwargs['configure_file'])
      def func_configure_file(self, node, args, kwargs):
          if len(args) > 0:
              raise InterpreterException("configure_file takes only keyword arguments.")
          if 'output' not in kwargs:
              raise InterpreterException('Required keyword argument "output" not defined.')
          actions = set(['configuration', 'command', 'copy']).intersection(kwargs.keys())
          if len(actions) == 0:
              raise InterpreterException('Must specify an action with one of these '
                                         'keyword arguments: \'configuration\', '
                                         '\'command\', or \'copy\'.')
          elif len(actions) == 2:
              raise InterpreterException('Must not specify both {!r} and {!r} '
                                         'keyword arguments since they are '
                                         'mutually exclusive.'.format(*actions))
          elif len(actions) == 3:
              raise InterpreterException('Must specify one of {!r}, {!r}, and '
                                         '{!r} keyword arguments since they are '
                                         'mutually exclusive.'.format(*actions))
          if 'capture' in kwargs:
              if not isinstance(kwargs['capture'], bool):
                  raise InterpreterException('"capture" keyword must be a boolean.')
              if 'command' not in kwargs:
                  raise InterpreterException('"capture" keyword requires "command" keyword.')
  
          if 'format' in kwargs:
              fmt = kwargs['format']
              if not isinstance(fmt, str):
                  raise InterpreterException('"format" keyword must be a string.')
          else:
              fmt = 'meson'
  
          if fmt not in ('meson', 'cmake', 'cmake@'):
              raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".')
  
          if 'output_format' in kwargs:
              output_format = kwargs['output_format']
              if not isinstance(output_format, str):
                  raise InterpreterException('"output_format" keyword must be a string.')
          else:
              output_format = 'c'
  
          if output_format not in ('c', 'nasm'):
              raise InterpreterException('"format" possible values are "c" or "nasm".')
  
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InterpreterException('depfile file name must be a string')
          else:
              depfile = None
  
          # Validate input
          inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
          inputs_abs = []
          for f in inputs:
              if isinstance(f, mesonlib.File):
                  inputs_abs.append(f.absolute_path(self.environment.source_dir,
                                                    self.environment.build_dir))
                  self.add_build_def_file(f)
              else:
                  raise InterpreterException('Inputs can only be strings or file objects')
          # Validate output
          output = kwargs['output']
          if not isinstance(output, str):
              raise InterpreterException('Output file name must be a string')
          if inputs_abs:
              values = mesonlib.get_filenames_templates_dict(inputs_abs, None)
              outputs = mesonlib.substitute_values([output], values)
              output = outputs[0]
              if depfile:
                  depfile = mesonlib.substitute_values([depfile], values)[0]
          ofile_rpath = os.path.join(self.subdir, output)
          if ofile_rpath in self.configure_file_outputs:
              mesonbuildfile = os.path.join(self.subdir, 'meson.build')
              current_call = "{}:{}".format(mesonbuildfile, self.current_lineno)
              first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath])
              mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call)
          else:
              self.configure_file_outputs[ofile_rpath] = self.current_lineno
          if os.path.dirname(output) != '':
              raise InterpreterException('Output file name must not contain a subdirectory.')
          (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
          ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname)
          # Perform the appropriate action
          if 'configuration' in kwargs:
              conf = kwargs['configuration']
              if isinstance(conf, dict):
                  FeatureNew('configure_file.configuration dictionary', '0.49.0').use(self.subproject)
                  conf = ConfigurationDataHolder(self.subproject, conf)
              elif not isinstance(conf, ConfigurationDataHolder):
                  raise InterpreterException('Argument "configuration" is not of type configuration_data')
              mlog.log('Configuring', mlog.bold(output), 'using configuration')
              if len(inputs) > 1:
                  raise InterpreterException('At most one input file can given in configuration mode')
              if inputs:
                  os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
                  file_encoding = kwargs.setdefault('encoding', 'utf-8')
                  missing_variables, confdata_useless = \
                      mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object,
                                            fmt, file_encoding)
                  if missing_variables:
                      var_list = ", ".join(map(repr, sorted(missing_variables)))
                      mlog.warning(
                          "The variable(s) %s in the input file '%s' are not "
                          "present in the given configuration data." % (
                              var_list, inputs[0]), location=node)
                  if confdata_useless:
                      ifbase = os.path.basename(inputs_abs[0])
                      mlog.warning('Got an empty configuration_data() object and found no '
                                   'substitutions in the input file {!r}. If you want to '
                                   'copy a file to the build dir, use the \'copy:\' keyword '
                                   'argument added in 0.47.0'.format(ifbase), location=node)
              else:
                  mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format)
              conf.mark_used()
          elif 'command' in kwargs:
              if len(inputs) > 1:
                  FeatureNew('multiple inputs in configure_file()', '0.52.0').use(self.subproject)
              # We use absolute paths for input and output here because the cwd
              # that the command is run from is 'unspecified', so it could change.
              # Currently it's builddir/subdir for in_builddir else srcdir/subdir.
              values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs])
              if depfile:
                  depfile = os.path.join(self.environment.get_scratch_dir(), depfile)
                  values['@DEPFILE@'] = depfile
              # Substitute @INPUT@, @OUTPUT@, etc here.
              cmd = mesonlib.substitute_values(kwargs['command'], values)
              mlog.log('Configuring', mlog.bold(output), 'with command')
              res = self.run_command_impl(node, cmd,  {}, True)
              if res.returncode != 0:
                  raise InterpreterException('Running configure command failed.\n%s\n%s' %
                                             (res.stdout, res.stderr))
              if 'capture' in kwargs and kwargs['capture']:
                  dst_tmp = ofile_abs + '~'
                  file_encoding = kwargs.setdefault('encoding', 'utf-8')
                  with open(dst_tmp, 'w', encoding=file_encoding) as f:
                      f.writelines(res.stdout)
                  if inputs_abs:
                      shutil.copymode(inputs_abs[0], dst_tmp)
                  mesonlib.replace_if_different(ofile_abs, dst_tmp)
              if depfile:
                  mlog.log('Reading depfile:', mlog.bold(depfile))
                  with open(depfile, 'r') as f:
                      df = DepFile(f.readlines())
                      deps = df.get_all_dependencies(ofile_fname)
                      for dep in deps:
                          self.add_build_def_file(dep)
  
          elif 'copy' in kwargs:
              if len(inputs_abs) != 1:
                  raise InterpreterException('Exactly one input file must be given in copy mode')
              os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
              shutil.copyfile(inputs_abs[0], ofile_abs)
              shutil.copystat(inputs_abs[0], ofile_abs)
          else:
              # Not reachable
              raise AssertionError
          # Install file if requested, we check for the empty string
          # for backwards compatibility. That was the behaviour before
          # 0.45.0 so preserve it.
          idir = kwargs.get('install_dir', '')
          if idir is False:
              idir = ''
              mlog.deprecation('Please use the new `install:` kwarg instead of passing '
                               '`false` to `install_dir:`', location=node)
          if not isinstance(idir, str):
              if isinstance(idir, list) and len(idir) == 0:
                  mlog.deprecation('install_dir: kwarg must be a string and not an empty array. '
                                   'Please use the install: kwarg to enable or disable installation. '
                                   'This will be a hard error in the next release.')
              else:
                  raise InterpreterException('"install_dir" must be a string')
          install = kwargs.get('install', idir != '')
          if not isinstance(install, bool):
              raise InterpreterException('"install" must be a boolean')
          if install:
              if not idir:
                  raise InterpreterException('"install_dir" must be specified '
                                             'when "install" in a configure_file '
                                             'is true')
              cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname)
              install_mode = self._get_kwarg_install_mode(kwargs)
              self.build.data.append(build.Data([cfile], idir, install_mode))
          return mesonlib.File.from_built_file(self.subdir, output)
  
      def extract_incdirs(self, kwargs):
          prospectives = unholder(extract_as_list(kwargs, 'include_directories'))
          result = []
          for p in prospectives:
              if isinstance(p, build.IncludeDirs):
                  result.append(p)
              elif isinstance(p, str):
                  result.append(self.build_incdir_object([p]).held_object)
              else:
                  raise InterpreterException('Include directory objects can only be created from strings or include directories.')
          return result
  
      @permittedKwargs(permitted_kwargs['include_directories'])
      @stringArgs
      def func_include_directories(self, node, args, kwargs):
          return self.build_incdir_object(args, kwargs.get('is_system', False))
  
      def build_incdir_object(self, incdir_strings, is_system=False):
          if not isinstance(is_system, bool):
              raise InvalidArguments('Is_system must be boolean.')
          src_root = self.environment.get_source_dir()
          build_root = self.environment.get_build_dir()
          absbase_src = os.path.join(src_root, self.subdir)
          absbase_build = os.path.join(build_root, self.subdir)
  
          for a in incdir_strings:
              if a.startswith(src_root):
                  raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use
  relative paths instead.
  
  To get include path to any directory relative to the current dir do
  
  incdir = include_directories(dirname)
  
  After this incdir will contain both the current source dir as well as the
  corresponding build dir. It can then be used in any subdirectory and
  Meson will take care of all the busywork to make paths work.
  
  Dirname can even be '.' to mark the current directory. Though you should
  remember that the current source and build directories are always
  put in the include directories by default so you only need to do
  include_directories('.') if you intend to use the result in a
  different subdirectory.
  ''')
              absdir_src = os.path.join(absbase_src, a)
              absdir_build = os.path.join(absbase_build, a)
              if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build):
                  raise InvalidArguments('Include dir %s does not exist.' % a)
          i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system))
          return i
  
      @permittedKwargs(permitted_kwargs['add_test_setup'])
      @stringArgs
      def func_add_test_setup(self, node, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Add_test_setup needs one argument for the setup name.')
          setup_name = args[0]
          if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None:
              raise InterpreterException('Setup name may only contain alphanumeric characters.')
          if ":" not in setup_name:
              setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name
          try:
              inp = unholder(extract_as_list(kwargs, 'exe_wrapper'))
              exe_wrapper = []
              for i in inp:
                  if isinstance(i, str):
                      exe_wrapper.append(i)
                  elif isinstance(i, dependencies.ExternalProgram):
                      if not i.found():
                          raise InterpreterException('Tried to use non-found executable.')
                      exe_wrapper += i.get_command()
                  else:
                      raise InterpreterException('Exe wrapper can only contain strings or external binaries.')
          except KeyError:
              exe_wrapper = None
          gdb = kwargs.get('gdb', False)
          if not isinstance(gdb, bool):
              raise InterpreterException('Gdb option must be a boolean')
          timeout_multiplier = kwargs.get('timeout_multiplier', 1)
          if not isinstance(timeout_multiplier, int):
              raise InterpreterException('Timeout multiplier must be a number.')
          is_default = kwargs.get('is_default', False)
          if not isinstance(is_default, bool):
              raise InterpreterException('is_default option must be a boolean')
          if is_default:
              if self.build.test_setup_default_name is not None:
                  raise InterpreterException('\'%s\' is already set as default. '
                                             'is_default can be set to true only once' % self.build.test_setup_default_name)
              self.build.test_setup_default_name = setup_name
          env = self.unpack_env_kwarg(kwargs)
          self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env)
  
      @permittedKwargs(permitted_kwargs['add_global_arguments'])
      @stringArgs
      def func_add_global_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_global_link_arguments'])
      @stringArgs
      def func_add_global_link_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_project_arguments'])
      @stringArgs
      def func_add_project_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_project_link_arguments'])
      @stringArgs
      def func_add_project_link_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs)
  
      def warn_about_builtin_args(self, args):
          warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic')
          optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os')
          for arg in args:
              if arg in warnargs:
                  mlog.warning('Consider using the built-in warning_level option instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg in optargs:
                  mlog.warning('Consider using the built-in optimization level instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg == '-g':
                  mlog.warning('Consider using the built-in debug option instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg == '-pipe':
                  mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.",
                               location=self.current_node)
              elif arg.startswith('-fsanitize'):
                  mlog.warning('Consider using the built-in option for sanitizers instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg.startswith('-std=') or arg.startswith('/std:'):
                  mlog.warning('Consider using the built-in option for language standard version instead of using "{}".'.format(arg),
                               location=self.current_node)
  
      def add_global_arguments(self, node, argsdict, args, kwargs):
          if self.is_subproject():
              msg = 'Function \'{}\' cannot be used in subprojects because ' \
                    'there is no way to make that reliable.\nPlease only call ' \
                    'this if is_subproject() returns false. Alternatively, ' \
                    'define a variable that\ncontains your language-specific ' \
                    'arguments and add it to the appropriate *_args kwarg ' \
                    'in each target.'.format(node.func_name)
              raise InvalidCode(msg)
          frozen = self.project_args_frozen or self.global_args_frozen
          self.add_arguments(node, argsdict, frozen, args, kwargs)
  
      def add_project_arguments(self, node, argsdict, args, kwargs):
          if self.subproject not in argsdict:
              argsdict[self.subproject] = {}
          self.add_arguments(node, argsdict[self.subproject],
                             self.project_args_frozen, args, kwargs)
  
      def add_arguments(self, node, argsdict, args_frozen, args, kwargs):
          if args_frozen:
              msg = 'Tried to use \'{}\' after a build target has been declared.\n' \
                    'This is not permitted. Please declare all ' \
                    'arguments before your targets.'.format(node.func_name)
              raise InvalidCode(msg)
  
          if 'language' not in kwargs:
              raise InvalidCode('Missing language definition in {}'.format(node.func_name))
  
          self.warn_about_builtin_args(args)
  
          for lang in mesonlib.stringlistify(kwargs['language']):
              lang = lang.lower()
              argsdict[lang] = argsdict.get(lang, []) + args
  
      @noKwargs
      @noArgsFlattening
      def func_environment(self, node, args, kwargs):
          if len(args) > 1:
              raise InterpreterException('environment takes only one optional positional arguments')
          elif len(args) == 1:
              FeatureNew('environment positional arguments', '0.52.0').use(self.subproject)
              initial_values = args[0]
              if not isinstance(initial_values, dict) and not isinstance(initial_values, list):
                  raise InterpreterException('environment first argument must be a dictionary or a list')
          else:
              initial_values = {}
          return EnvironmentVariablesHolder(initial_values)
  
      @stringArgs
      @noKwargs
      def func_join_paths(self, node, args, kwargs):
          return self.join_path_strings(args)
  
      def run(self):
          super().run()
          mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
          FeatureNew.report(self.subproject)
          FeatureDeprecated.report(self.subproject)
          if not self.is_subproject():
              self.print_extra_warnings()
          if self.subproject == '':
              self._print_summary()
  
      def print_extra_warnings(self):
          # TODO cross compilation
          for c in self.coredata.compilers.host.values():
              if c.get_id() == 'clang':
                  self.check_clang_asan_lundef()
                  break
  
      def check_clang_asan_lundef(self):
          if 'b_lundef' not in self.coredata.base_options:
              return
          if 'b_sanitize' not in self.coredata.base_options:
              return
          if (self.coredata.base_options['b_lundef'].value and
                  self.coredata.base_options['b_sanitize'].value != 'none'):
              mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef.
  This will probably not work.
  Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value),
                           location=self.current_node)
  
      def evaluate_subproject_info(self, path_from_source_root, subproject_dirname):
          depth = 0
          subproj_name = ''
          segs = PurePath(path_from_source_root).parts
          segs_spd = PurePath(subproject_dirname).parts
          while segs and segs[0] == segs_spd[0]:
              if len(segs_spd) == 1:
                  subproj_name = segs[1]
                  segs = segs[2:]
                  depth += 1
              else:
                  segs_spd = segs_spd[1:]
                  segs = segs[1:]
          return (depth, subproj_name)
  
      # Check that the indicated file is within the same subproject
      # as we currently are. This is to stop people doing
      # nasty things like:
      #
      # f = files('../../master_src/file.c')
      #
      # Note that this is validated only when the file
      # object is generated. The result can be used in a different
      # subproject than it is defined in (due to e.g. a
      # declare_dependency).
      def validate_within_subproject(self, subdir, fname):
          norm = os.path.normpath(os.path.join(subdir, fname))
          if os.path.isabs(norm):
              if not norm.startswith(self.environment.source_dir):
                  # Grabbing files outside the source tree is ok.
                  # This is for vendor stuff like:
                  #
                  # /opt/vendorsdk/src/file_with_license_restrictions.c
                  return
              norm = os.path.relpath(norm, self.environment.source_dir)
              assert(not os.path.isabs(norm))
          (num_sps, sproj_name) = self.evaluate_subproject_info(norm, self.subproject_dir)
          plain_filename = os.path.basename(norm)
          if num_sps == 0:
              if not self.is_subproject():
                  return
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
          if num_sps > 1:
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a nested subproject.' % plain_filename)
          if sproj_name != self.subproject_directory_name:
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
  
      def source_strings_to_files(self, sources):
          results = []
          mesonlib.check_direntry_issues(sources)
          if not isinstance(sources, list):
              sources = [sources]
          for s in sources:
              if isinstance(s, (mesonlib.File, GeneratedListHolder,
                                TargetHolder, CustomTargetIndexHolder,
                                GeneratedObjectsHolder)):
                  pass
              elif isinstance(s, str):
                  self.validate_within_subproject(self.subdir, s)
                  s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s)
              else:
                  raise InterpreterException('Source item is {!r} instead of '
                                             'string or File-type object'.format(s))
              results.append(s)
          return results
  
      def add_target(self, name, tobj):
          if name == '':
              raise InterpreterException('Target name must not be empty.')
          if name.strip() == '':
              raise InterpreterException('Target name must not consist only of whitespace.')
          if name.startswith('meson-'):
              raise InvalidArguments("Target names starting with 'meson-' are reserved "
                                     "for Meson's internal use. Please rename.")
          if name in coredata.forbidden_target_names:
              raise InvalidArguments("Target name '%s' is reserved for Meson's "
                                     "internal use. Please rename." % name)
          # To permit an executable and a shared library to have the
          # same name, such as "foo.exe" and "libfoo.a".
          idname = tobj.get_id()
          if idname in self.build.targets:
              raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name)
          self.build.targets[idname] = tobj
          if idname not in self.coredata.target_guids:
              self.coredata.target_guids[idname] = str(uuid.uuid4()).upper()
  
      @FeatureNew('both_libraries', '0.46.0')
      def build_both_libraries(self, node, args, kwargs):
          shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
  
          # Check if user forces non-PIC static library.
          pic = True
          if 'pic' in kwargs:
              pic = kwargs['pic']
          elif 'b_staticpic' in self.environment.coredata.base_options:
              pic = self.environment.coredata.base_options['b_staticpic'].value
  
          if pic:
              # Exclude sources from args and kwargs to avoid building them twice
              static_args = [args[0]]
              static_kwargs = kwargs.copy()
              static_kwargs['sources'] = []
              static_kwargs['objects'] = shared_holder.held_object.extract_all_objects()
          else:
              static_args = args
              static_kwargs = kwargs
  
          static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder)
  
          return BothLibrariesHolder(shared_holder, static_holder, self)
  
      def build_library(self, node, args, kwargs):
          default_library = self.coredata.get_builtin_option('default_library', self.subproject)
          if default_library == 'shared':
              return self.build_target(node, args, kwargs, SharedLibraryHolder)
          elif default_library == 'static':
              return self.build_target(node, args, kwargs, StaticLibraryHolder)
          elif default_library == 'both':
              return self.build_both_libraries(node, args, kwargs)
          else:
              raise InterpreterException('Unknown default_library value: %s.', default_library)
  
      def build_target(self, node, args, kwargs, targetholder):
          @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
          @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
          @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
          @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility'])
          def build_target_decorator_caller(self, node, args, kwargs):
              return True
  
          build_target_decorator_caller(self, node, args, kwargs)
  
          if not args:
              raise InterpreterException('Target does not have a name.')
          name, *sources = args
          for_machine = self.machine_from_native_kwarg(kwargs)
          if 'sources' in kwargs:
              sources += listify(kwargs['sources'])
          sources = self.source_strings_to_files(sources)
          objs = extract_as_list(kwargs, 'objects')
          kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          if 'extra_files' in kwargs:
              ef = extract_as_list(kwargs, 'extra_files')
              kwargs['extra_files'] = self.source_strings_to_files(ef)
          self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources)
          if targetholder == ExecutableHolder:
              targetclass = build.Executable
          elif targetholder == SharedLibraryHolder:
              targetclass = build.SharedLibrary
          elif targetholder == SharedModuleHolder:
              targetclass = build.SharedModule
          elif targetholder == StaticLibraryHolder:
              targetclass = build.StaticLibrary
          elif targetholder == JarHolder:
              targetclass = build.Jar
          else:
              mlog.debug('Unknown target type:', str(targetholder))
              raise RuntimeError('Unreachable code')
          self.kwarg_strings_to_includedirs(kwargs)
  
          # Filter out kwargs from other target types. For example 'soversion'
          # passed to library() when default_library == 'static'.
          kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
  
          kwargs['include_directories'] = self.extract_incdirs(kwargs)
          target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
          target.project_version = self.project_version
  
          if not self.environment.machines.matches_build_machine(for_machine):
              self.add_cross_stdlib_info(target)
          l = targetholder(target, self)
          self.add_target(name, l.held_object)
          self.project_args_frozen = True
          return l
  
      def kwarg_strings_to_includedirs(self, kwargs):
          if 'd_import_dirs' in kwargs:
              items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
              cleaned_items = []
              for i in items:
                  if isinstance(i, str):
                      # BW compatibility. This was permitted so we must support it
                      # for a few releases so people can transition to "correct"
                      # path declarations.
                      if os.path.normpath(i).startswith(self.environment.get_source_dir()):
                          mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead.
  This will become a hard error in the future.''', location=self.current_node)
                          i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir))
                          i = self.build_incdir_object([i])
                  cleaned_items.append(i)
              kwargs['d_import_dirs'] = cleaned_items
  
      def get_used_languages(self, target):
          result = {}
          for i in target.sources:
              # TODO other platforms
              for lang, c in self.coredata.compilers.host.items():
                  if c.can_compile(i):
                      result[lang] = True
                      break
          return result
  
      def add_cross_stdlib_info(self, target):
          if target.for_machine != MachineChoice.HOST:
              return
          for l in self.get_used_languages(target):
              props = self.environment.properties.host
              if props.has_stdlib(l) \
                      and self.subproject != props.get_stdlib(l)[0]:
                  target.add_deps(self.build.stdlibs.host[l])
  
      def check_sources_exist(self, subdir, sources):
          for s in sources:
              if not isinstance(s, str):
                  continue # This means a generated source and they always exist.
              fname = os.path.join(subdir, s)
              if not os.path.isfile(fname):
                  raise InterpreterException('Tried to add non-existing source file %s.' % s)
  
      # Only permit object extraction from the same subproject
      def validate_extraction(self, buildtarget: InterpreterObject) -> None:
          if not self.subdir.startswith(self.subproject_dir):
              if buildtarget.subdir.startswith(self.subproject_dir):
                  raise InterpreterException('Tried to extract objects from a subproject target.')
          else:
              if not buildtarget.subdir.startswith(self.subproject_dir):
                  raise InterpreterException('Tried to extract objects from the main project from a subproject.')
              if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
                  raise InterpreterException('Tried to extract objects from a different subproject.')
  
      def is_subproject(self):
          return self.subproject != ''
  
      @noKwargs
      @noArgsFlattening
      def func_set_variable(self, node, args, kwargs):
          if len(args) != 2:
              raise InvalidCode('Set_variable takes two arguments.')
          varname, value = args
          self.set_variable(varname, value)
  
      @noKwargs
      @noArgsFlattening
      def func_get_variable(self, node, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InvalidCode('Get_variable takes one or two arguments.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('First argument must be a string.')
          try:
              return self.variables[varname]
          except KeyError:
              pass
          if len(args) == 2:
              return args[1]
          raise InterpreterException('Tried to get unknown variable "%s".' % varname)
  
      @stringArgs
      @noKwargs
      def func_is_variable(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Is_variable takes two arguments.')
          varname = args[0]
          return varname in self.variables
  
      @staticmethod
      def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice:
          native = kwargs.get('native', False)
          if not isinstance(native, bool):
              raise InvalidArguments('Argument to "native" must be a boolean.')
          return MachineChoice.BUILD if native else MachineChoice.HOST
  
      @FeatureNew('is_disabler', '0.52.0')
      @noKwargs
      def func_is_disabler(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Is_disabler takes one argument.')
          varname = args[0]
          return isinstance(varname, Disabler)
  
files: mesonbuild/interpreter.py
files: # Copyright 2012-2019 The Meson development team
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mparser
  from . import environment
  from . import coredata
  from . import dependencies
  from . import mlog
  from . import build
  from . import optinterpreter
  from . import compilers
  from .wrap import wrap, WrapMode
  from . import mesonlib
  from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep, unholder
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  from .depfile import DepFile
  from .interpreterbase import InterpreterBase
  from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
  from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
  from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .interpreterbase import ObjectHolder
  from .modules import ModuleReturnValue
  from .cmake import CMakeInterpreter
  
  from pathlib import Path, PurePath
  import os
  import shutil
  import uuid
  import re
  import shlex
  import subprocess
  import collections
  import functools
  import typing as T
  
  import importlib
  
  permitted_method_kwargs = {
      'partial_dependency': {'compile_args', 'link_args', 'links', 'includes',
                             'sources'},
  }
  
  def stringifyUserArguments(args):
      if isinstance(args, list):
          return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
      elif isinstance(args, dict):
          return '{%s}' % ', '.join(['%s : %s' % (stringifyUserArguments(k), stringifyUserArguments(v)) for k, v in args.items()])
      elif isinstance(args, int):
          return str(args)
      elif isinstance(args, str):
          return "'%s'" % args
      raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
  
  
  class OverrideProgram(dependencies.ExternalProgram):
      pass
  
  
  class FeatureOptionHolder(InterpreterObject, ObjectHolder):
      def __init__(self, env, name, option):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, option)
          if option.is_auto():
              self.held_object = env.coredata.builtins['auto_features']
          self.name = name
          self.methods.update({'enabled': self.enabled_method,
                               'disabled': self.disabled_method,
                               'auto': self.auto_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def enabled_method(self, args, kwargs):
          return self.held_object.is_enabled()
  
      @noPosargs
      @permittedKwargs({})
      def disabled_method(self, args, kwargs):
          return self.held_object.is_disabled()
  
      @noPosargs
      @permittedKwargs({})
      def auto_method(self, args, kwargs):
          return self.held_object.is_auto()
  
  def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True):
      val = kwargs.get('required', default)
      disabled = False
      required = False
      feature = None
      if isinstance(val, FeatureOptionHolder):
          if not feature_check:
              feature_check = FeatureNew('User option "feature"', '0.47.0')
          feature_check.use(subproject)
          option = val.held_object
          feature = val.name
          if option.is_disabled():
              disabled = True
          elif option.is_enabled():
              required = True
      elif isinstance(val, bool):
          required = val
      else:
          raise InterpreterException('required keyword argument must be boolean or a feature option')
  
      # Keep boolean value in kwargs to simplify other places where this kwarg is
      # checked.
      kwargs['required'] = required
  
      return disabled, required, feature
  
  def extract_search_dirs(kwargs):
      search_dirs = mesonlib.stringlistify(kwargs.get('dirs', []))
      search_dirs = [Path(d).expanduser() for d in search_dirs]
      for d in search_dirs:
          if mesonlib.is_windows() and d.root.startswith('\\'):
              # a Unix-path starting with `/` that is not absolute on Windows.
              # discard without failing for end-user ease of cross-platform directory arrays
              continue
          if not d.is_absolute():
              raise InvalidCode('Search directory {} is not an absolute path.'.format(d))
      return list(map(str, search_dirs))
  
  class TryRunResultHolder(InterpreterObject):
      def __init__(self, res):
          super().__init__()
          self.res = res
          self.methods.update({'returncode': self.returncode_method,
                               'compiled': self.compiled_method,
                               'stdout': self.stdout_method,
                               'stderr': self.stderr_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.res.returncode
  
      @noPosargs
      @permittedKwargs({})
      def compiled_method(self, args, kwargs):
          return self.res.compiled
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.res.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.res.stderr
  
  class RunProcess(InterpreterObject):
  
      def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          self.capture = capture
          pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
          self.returncode = pc.returncode
          self.methods.update({'returncode': self.returncode_method,
                               'stdout': self.stdout_method,
                               'stderr': self.stderr_method,
                               })
  
      def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False):
          command_array = cmd.get_command() + args
          menv = {'MESON_SOURCE_ROOT': source_dir,
                  'MESON_BUILD_ROOT': build_dir,
                  'MESON_SUBDIR': subdir,
                  'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]),
                  }
          if in_builddir:
              cwd = os.path.join(build_dir, subdir)
          else:
              cwd = os.path.join(source_dir, subdir)
          child_env = os.environ.copy()
          child_env.update(menv)
          child_env = env.get_env(child_env)
          stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
              if self.capture:
                  mlog.debug('--- stdout ---')
                  mlog.debug(o)
              else:
                  o = ''
                  mlog.debug('--- stdout disabled ---')
              mlog.debug('--- stderr ---')
              mlog.debug(e)
              mlog.debug('')
  
              if check and p.returncode != 0:
                  raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
  
              return p, o, e
          except FileNotFoundError:
              raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.returncode
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.stderr
  
  class ConfigureFileHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          InterpreterObject.__init__(self)
          obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data)
          ObjectHolder.__init__(self, obj)
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self, initial_values=None):
          MutableInterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.EnvironmentVariables())
          self.methods.update({'set': self.set_method,
                               'append': self.append_method,
                               'prepend': self.prepend_method,
                               })
          if isinstance(initial_values, dict):
              for k, v in initial_values.items():
                  self.set_method([k, v], {})
          elif isinstance(initial_values, list):
              for e in initial_values:
                  if '=' not in e:
                      raise InterpreterException('Env var definition must be of type key=val.')
                  (k, val) = e.split('=', 1)
                  k = k.strip()
                  val = val.strip()
                  if ' ' in k:
                      raise InterpreterException('Env var key must not have spaces in it.')
                  self.set_method([k, val], {})
          elif initial_values:
              raise AssertionError('Unsupported EnvironmentVariablesHolder initial_values')
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.held_object.envvars)
  
      def add_var(self, method, args, kwargs):
          if not isinstance(kwargs.get("separator", ""), str):
              raise InterpreterException("EnvironmentVariablesHolder methods 'separator'"
                                         " argument needs to be a string.")
          if len(args) < 2:
              raise InterpreterException("EnvironmentVariablesHolder methods require at least"
                                         "2 arguments, first is the name of the variable and"
                                         " following one are values")
          # Warn when someone tries to use append() or prepend() on an env var
          # which already has an operation set on it. People seem to think that
          # multiple append/prepend operations stack, but they don't.
          if method != self.held_object.set and self.held_object.has_name(args[0]):
              mlog.warning('Overriding previous value of environment variable {!r} with a new one'
                           .format(args[0]), location=self.current_node)
          self.held_object.add_var(method, args[0], args[1:], kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def set_method(self, args, kwargs):
          self.add_var(self.held_object.set, args, kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def append_method(self, args, kwargs):
          self.add_var(self.held_object.append, args, kwargs)
  
      @stringArgs
      @permittedKwargs({'separator'})
      def prepend_method(self, args, kwargs):
          self.add_var(self.held_object.prepend, args, kwargs)
  
  
  class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self, pv, initial_values=None):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData(), pv)
          self.methods.update({'set': self.set_method,
                               'set10': self.set10_method,
                               'set_quoted': self.set_quoted_method,
                               'has': self.has_method,
                               'get': self.get_method,
                               'get_unquoted': self.get_unquoted_method,
                               'merge_from': self.merge_from_method,
                               })
          if isinstance(initial_values, dict):
              for k, v in initial_values.items():
                  self.set_method([k, v], {})
          elif initial_values:
              raise AssertionError('Unsupported ConfigurationDataHolder initial_values')
  
      def is_used(self):
          return self.used
  
      def mark_used(self):
          self.used = True
  
      def validate_args(self, args, kwargs):
          if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
              mlog.deprecation('Passing a list as the single argument to '
                               'configuration_data.set is deprecated. This will '
                               'become a hard error in the future.',
                               location=self.current_node)
              args = args[0]
  
          if len(args) != 2:
              raise InterpreterException("Configuration set requires 2 arguments.")
          if self.used:
              raise InterpreterException("Can not set values on configuration object that has been used.")
          name, val = args
          if not isinstance(val, (int, str)):
              msg = 'Setting a configuration data value to {!r} is invalid, ' \
                    'and will fail at configure_file(). If you are using it ' \
                    'just to store some values, please use a dict instead.'
              mlog.deprecation(msg.format(val), location=self.current_node)
          desc = kwargs.get('description', None)
          if not isinstance(name, str):
              raise InterpreterException("First argument to set must be a string.")
          if desc is not None and not isinstance(desc, str):
              raise InterpreterException('Description must be a string.')
  
          return name, val, desc
  
      @noArgsFlattening
      def set_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          self.held_object.values[name] = (val, desc)
  
      def set_quoted_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          if not isinstance(val, str):
              raise InterpreterException("Second argument to set_quoted must be a string.")
          escaped_val = '\\"'.join(val.split('"'))
          self.held_object.values[name] = ('"' + escaped_val + '"', desc)
  
      def set10_method(self, args, kwargs):
          (name, val, desc) = self.validate_args(args, kwargs)
          if val:
              self.held_object.values[name] = (1, desc)
          else:
              self.held_object.values[name] = (0, desc)
  
      def has_method(self, args, kwargs):
          return args[0] in self.held_object.values
  
      @FeatureNew('configuration_data.get()', '0.38.0')
      @noArgsFlattening
      def get_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              return self.held_object.get(name)[0]
          if len(args) > 1:
              return args[1]
          raise InterpreterException('Entry %s not in configuration data.' % name)
  
      @FeatureNew('configuration_data.get_unquoted()', '0.44.0')
      def get_unquoted_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              val = self.held_object.get(name)[0]
          elif len(args) > 1:
              val = args[1]
          else:
              raise InterpreterException('Entry %s not in configuration data.' % name)
          if val[0] == '"' and val[-1] == '"':
              return val[1:-1]
          return val
  
      def get(self, name):
          return self.held_object.values[name] # (val, desc)
  
      def keys(self):
          return self.held_object.values.keys()
  
      def merge_from_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Merge_from takes one positional argument.')
          from_object = args[0]
          if not isinstance(from_object, ConfigurationDataHolder):
              raise InterpreterException('Merge_from argument must be a configuration data object.')
          from_object = from_object.held_object
          for k, v in from_object.values.items():
              self.held_object.values[k] = v
  
  # Interpreter objects can not be pickled so we must have
  # these wrappers.
  
  class DependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               'name': self.name_method,
                               'get_pkgconfig_variable': self.pkgconfig_method,
                               'get_configtool_variable': self.configtool_method,
                               'get_variable': self.variable_method,
                               'partial_dependency': self.partial_dependency_method,
                               'include_type': self.include_type_method,
                               'as_system': self.as_system_method,
                               })
  
      def found(self):
          return self.found_method([], {})
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          if self.held_object.type_name == 'internal':
              return True
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @noPosargs
      @permittedKwargs({})
      def name_method(self, args, kwargs):
          return self.held_object.get_name()
  
      @permittedKwargs({'define_variable', 'default'})
      def pkgconfig_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_pkgconfig_variable(varname, kwargs)
  
      @FeatureNew('dep.get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
      @FeatureNew('dep.get_variable', '0.51.0')
      @noPosargs
      @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'})
      @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal'])
      def variable_method(self, args, kwargs):
          return self.held_object.get_variable(**kwargs)
  
      @FeatureNew('dep.include_type', '0.52.0')
      @noPosargs
      @permittedKwargs({})
      def include_type_method(self, args, kwargs):
          return self.held_object.get_include_type()
  
      @FeatureNew('dep.as_system', '0.52.0')
      @permittedKwargs({})
      def as_system_method(self, args, kwargs):
          args = listify(args)
          new_is_system = 'system'
          if len(args) > 1:
              raise InterpreterException('as_system takes only one optional value')
          if len(args) == 1:
              new_is_system = args[0]
          new_dep = self.held_object.generate_system_dependency(new_is_system)
          return DependencyHolder(new_dep, self.subproject)
  
  class ExternalProgramHolder(InterpreterObject, ObjectHolder):
      def __init__(self, ep, subproject, backend=None):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, ep)
          self.subproject = subproject
          self.backend = backend
          self.methods.update({'found': self.found_method,
                               'path': self.path_method,
                               'full_path': self.full_path_method})
          self.cached_version = None
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      @noPosargs
      @permittedKwargs({})
      def path_method(self, args, kwargs):
          mlog.deprecation('path() method is deprecated and replaced by full_path()')
          return self._full_path()
  
      @noPosargs
      @permittedKwargs({})
      @FeatureNew('ExternalProgram.full_path', '0.55.0')
      def full_path_method(self, args, kwargs):
          return self._full_path()
  
      def _full_path(self):
          exe = self.held_object
          if isinstance(exe, build.Executable):
              return self.backend.get_target_filename_abs(exe)
          return exe.get_path()
  
      def found(self):
          return isinstance(self.held_object, build.Executable) or self.held_object.found()
  
      def get_command(self):
          return self.held_object.get_command()
  
      def get_name(self):
          exe = self.held_object
          if isinstance(exe, build.Executable):
              return exe.name
          return exe.get_name()
  
      def get_version(self, interpreter):
          if isinstance(self.held_object, build.Executable):
              return self.held_object.project_version
          if not self.cached_version:
              raw_cmd = self.get_command() + ['--version']
              cmd = [self, '--version']
              res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True)
              if res.returncode != 0:
                  m = 'Running {!r} failed'
                  raise InterpreterException(m.format(raw_cmd))
              output = res.stdout.strip()
              if not output:
                  output = res.stderr.strip()
              match = re.search(r'([0-9][0-9\.]+)', output)
              if not match:
                  m = 'Could not find a version number in output of {!r}'
                  raise InterpreterException(m.format(raw_cmd))
              self.cached_version = match.group(1)
          return self.cached_version
  
  class ExternalLibraryHolder(InterpreterObject, ObjectHolder):
      def __init__(self, el, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      def found(self):
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def get_name(self):
          return self.held_object.name
  
      def get_compile_args(self):
          return self.held_object.get_compile_args()
  
      def get_link_args(self):
          return self.held_object.get_link_args()
  
      def get_exe_args(self):
          return self.held_object.get_exe_args()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      @FeatureNewKwargs('generator', '0.43.0', ['capture'])
      def __init__(self, interp, args, kwargs):
          self.interpreter = interp
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject)
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=None):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else []))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  # A machine that's statically known from the cross file
  class MachineHolder(InterpreterObject, ObjectHolder):
      def __init__(self, machine_info):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, machine_info)
          self.methods.update({'system': self.system_method,
                               'cpu': self.cpu_method,
                               'cpu_family': self.cpu_family_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  class IncludeDirsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, idobj):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, idobj)
  
  class Headers(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.install_subdir = kwargs.get('subdir', '')
          if os.path.isabs(self.install_subdir):
              mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.')
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InterpreterException('Custom_install_dir must be a string.')
  
      def set_install_subdir(self, subdir):
          self.install_subdir = subdir
  
      def get_install_subdir(self):
          return self.install_subdir
  
      def get_sources(self):
          return self.sources
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
  class DataHolder(InterpreterObject, ObjectHolder):
      def __init__(self, data):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, data)
  
      def get_source_subdir(self):
          return self.held_object.source_subdir
  
      def get_sources(self):
          return self.held_object.sources
  
      def get_install_dir(self):
          return self.held_object.install_dir
  
  class InstallDir(InterpreterObject):
      def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory):
          InterpreterObject.__init__(self)
          self.source_subdir = src_subdir
          self.installable_subdir = inst_subdir
          self.install_dir = install_dir
          self.install_mode = install_mode
          self.exclude = exclude
          self.strip_directory = strip_directory
  
  class Man(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.validate_sources()
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str):
              raise InterpreterException('Custom_install_dir must be a string.')
  
      def validate_sources(self):
          for s in self.sources:
              try:
                  num = int(s.split('.')[-1])
              except (IndexError, ValueError):
                  num = 0
              if num < 1 or num > 8:
                  raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
      def get_sources(self):
          return self.sources
  
  class GeneratedObjectsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, held_object):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, held_object)
  
  class TargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, target, interp):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, target, interp.subproject)
          self.interpreter = interp
  
  class BuildTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'extract_objects': self.extract_objects_method,
                               'extract_all_objects': self.extract_all_objects_method,
                               'name': self.name_method,
                               'get_id': self.get_id_method,
                               'outdir': self.outdir_method,
                               'full_path': self.full_path_method,
                               'private_dir_include': self.private_dir_include_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.filename)
  
      def is_cross(self):
          return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine)
  
      @noPosargs
      @permittedKwargs({})
      def private_dir_include_method(self, args, kwargs):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [self.interpreter.backend.get_target_private_dir(self.held_object)]))
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @noPosargs
      @permittedKwargs({})
      def outdir_method(self, args, kwargs):
          return self.interpreter.backend.get_target_dir(self.held_object)
  
      @permittedKwargs({})
      def extract_objects_method(self, args, kwargs):
          gobjs = self.held_object.extract_objects(args)
          return GeneratedObjectsHolder(gobjs)
  
      @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive'])
      @noPosargs
      @permittedKwargs({'recursive'})
      def extract_all_objects_method(self, args, kwargs):
          recursive = kwargs.get('recursive', False)
          gobjs = self.held_object.extract_all_objects(recursive)
          if gobjs.objlist and 'recursive' not in kwargs:
              mlog.warning('extract_all_objects called without setting recursive '
                           'keyword argument. Meson currently defaults to '
                           'non-recursive to maintain backward compatibility but '
                           'the default will be changed in the future.',
                           location=self.current_node)
          return GeneratedObjectsHolder(gobjs)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.held_object.get_id()
  
      @FeatureNew('name', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def name_method(self, args, kwargs):
          return self.held_object.name
  
  class ExecutableHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class StaticLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class SharedLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          # Set to True only when called from self.func_shared_lib().
          target.shared_library_only = False
  
  class BothLibrariesHolder(BuildTargetHolder):
      def __init__(self, shared_holder, static_holder, interp):
          # FIXME: This build target always represents the shared library, but
          # that should be configurable.
          super().__init__(shared_holder.held_object, interp)
          self.shared_holder = shared_holder
          self.static_holder = static_holder
          self.methods.update({'get_shared_lib': self.get_shared_lib_method,
                               'get_static_lib': self.get_static_lib_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}, {}: {}>'
          h1 = self.shared_holder.held_object
          h2 = self.static_holder.held_object
          return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
  
      @noPosargs
      @permittedKwargs({})
      def get_shared_lib_method(self, args, kwargs):
          return self.shared_holder
  
      @noPosargs
      @permittedKwargs({})
      def get_static_lib_method(self, args, kwargs):
          return self.static_holder
  
  class SharedModuleHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class JarHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class CustomTargetIndexHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               })
  
      @FeatureNew('custom_target[i].full_path', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
  class CustomTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               'to_list': self.to_list_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @FeatureNew('custom_target.to_list', '0.54.0')
      @noPosargs
      @permittedKwargs({})
      def to_list_method(self, args, kwargs):
          result = []
          for i in self.held_object:
              result.append(CustomTargetIndexHolder(i, self.interpreter))
          return result
  
      def __getitem__(self, index):
          return CustomTargetIndexHolder(self.held_object[index], self.interpreter)
  
      def __setitem__(self, index, value):  # lgtm[py/unexpected-raise-in-special-method]
          raise InterpreterException('Cannot set a member of a CustomTarget')
  
      def __delitem__(self, index):  # lgtm[py/unexpected-raise-in-special-method]
          raise InterpreterException('Cannot delete a member of a CustomTarget')
  
      def outdir_include(self):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))]))
  
  class RunTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
  class Test(InterpreterObject):
      def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable,
                   depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
                   is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables,
                   should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
                   priority: int):
          InterpreterObject.__init__(self)
          self.name = name
          self.suite = suite
          self.project_name = project
          self.exe = exe
          self.depends = depends
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.protocol = protocol
          self.priority = priority
  
      def get_exe(self):
          return self.exe
  
      def get_name(self):
          return self.name
  
  class SubprojectHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subinterpreter, subproject_dir, name, warnings=0, disabled_feature=None,
                   exception=None):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, subinterpreter)
          self.name = name
          self.warnings = warnings
          self.disabled_feature = disabled_feature
          self.exception = exception
          self.subproject_dir = subproject_dir
          self.methods.update({'get_variable': self.get_variable_method,
                               'found': self.found_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def found(self):
          return self.held_object is not None
  
      @permittedKwargs({})
      @noArgsFlattening
      def get_variable_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get_variable takes one or two arguments.')
          if not self.found():
              raise InterpreterException('Subproject "%s/%s" disabled can\'t get_variable on it.' % (
                  self.subproject_dir, self.name))
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Get_variable first argument must be a string.')
          try:
              return self.held_object.variables[varname]
          except KeyError:
              pass
  
          if len(args) == 2:
              return args[1]
  
          raise InvalidArguments('Requested variable "{0}" not found.'.format(varname))
  
  header_permitted_kwargs = set([
      'required',
      'prefix',
      'no_builtin_args',
      'include_directories',
      'args',
      'dependencies',
  ])
  
  find_library_permitted_kwargs = set([
      'has_headers',
      'required',
      'dirs',
      'static',
  ])
  
  find_library_permitted_kwargs |= set(['header_' + k for k in header_permitted_kwargs])
  
  class CompilerHolder(InterpreterObject):
      def __init__(self, compiler, env, subproject):
          InterpreterObject.__init__(self)
          self.compiler = compiler
          self.environment = env
          self.subproject = subproject
          self.methods.update({'compiles': self.compiles_method,
                               'links': self.links_method,
                               'get_id': self.get_id_method,
                               'get_linker_id': self.get_linker_id_method,
                               'compute_int': self.compute_int_method,
                               'sizeof': self.sizeof_method,
                               'get_define': self.get_define_method,
                               'check_header': self.check_header_method,
                               'has_header': self.has_header_method,
                               'has_header_symbol': self.has_header_symbol_method,
                               'run': self.run_method,
                               'has_function': self.has_function_method,
                               'has_member': self.has_member_method,
                               'has_members': self.has_members_method,
                               'has_type': self.has_type_method,
                               'alignment': self.alignment_method,
                               'version': self.version_method,
                               'cmd_array': self.cmd_array_method,
                               'find_library': self.find_library_method,
                               'has_argument': self.has_argument_method,
                               'has_function_attribute': self.has_func_attribute_method,
                               'get_supported_function_attributes': self.get_supported_function_attributes_method,
                               'has_multi_arguments': self.has_multi_arguments_method,
                               'get_supported_arguments': self.get_supported_arguments_method,
                               'first_supported_argument': self.first_supported_argument_method,
                               'has_link_argument': self.has_link_argument_method,
                               'has_multi_link_arguments': self.has_multi_link_arguments_method,
                               'get_supported_link_arguments': self.get_supported_link_arguments_method,
                               'first_supported_link_argument': self.first_supported_link_argument_method,
                               'unittest_args': self.unittest_args_method,
                               'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method,
                               'get_argument_syntax': self.get_argument_syntax_method,
                               })
  
      def _dep_msg(self, deps, endl):
          msg_single = 'with dependency {}'
          msg_many = 'with dependencies {}'
          if not deps:
              return endl
          if endl is None:
              endl = ''
          tpl = msg_many if len(deps) > 1 else msg_single
          names = []
          for d in deps:
              if isinstance(d, dependencies.ExternalLibrary):
                  name = '-l' + d.name
              else:
                  name = d.name
              names.append(name)
          return tpl.format(', '.join(names)) + endl
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.compiler.version
  
      @noPosargs
      @permittedKwargs({})
      def cmd_array_method(self, args, kwargs):
          return self.compiler.exelist
  
      def determine_args(self, kwargs, mode='link'):
          nobuiltins = kwargs.get('no_builtin_args', False)
          if not isinstance(nobuiltins, bool):
              raise InterpreterException('Type of no_builtin_args not a boolean.')
          args = []
          incdirs = extract_as_list(kwargs, 'include_directories')
          for i in incdirs:
              if not isinstance(i, IncludeDirsHolder):
                  raise InterpreterException('Include directories argument must be an include_directories object.')
              for idir in i.held_object.get_incdirs():
                  idir = os.path.join(self.environment.get_source_dir(),
                                      i.held_object.get_curdir(), idir)
                  args += self.compiler.get_include_args(idir, False)
          if not nobuiltins:
              for_machine = Interpreter.machine_from_native_kwarg(kwargs)
              opts = self.environment.coredata.compiler_options[for_machine][self.compiler.language]
              args += self.compiler.get_option_compile_args(opts)
              if mode == 'link':
                  args += self.compiler.get_option_link_args(opts)
          args += mesonlib.stringlistify(kwargs.get('args', []))
          return args
  
      def determine_dependencies(self, kwargs, endl=':'):
          deps = kwargs.get('dependencies', None)
          if deps is not None:
              deps = listify(deps)
              final_deps = []
              for d in deps:
                  try:
                      d = d.held_object
                  except Exception:
                      pass
                  if isinstance(d, InternalDependency) or not isinstance(d, Dependency):
                      raise InterpreterException('Dependencies must be external dependencies')
                  final_deps.append(d)
              deps = final_deps
          return deps, self._dep_msg(deps, endl)
  
      @permittedKwargs({
          'prefix',
          'args',
          'dependencies',
      })
      def alignment_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Alignment method takes exactly one positional argument.')
          check_stringlist(args)
          typename = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of alignment must be a string.')
          extra_args = mesonlib.stringlistify(kwargs.get('args', []))
          deps, msg = self.determine_dependencies(kwargs)
          result = self.compiler.alignment(typename, prefix, self.environment,
                                           extra_args=extra_args,
                                           dependencies=deps)
          mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result)
          return result
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def run_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Run method takes exactly one positional argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result = self.compiler.run(code, self.environment, extra_args=extra_args,
                                     dependencies=deps)
          if len(testname) > 0:
              if not result.compiled:
                  h = mlog.red('DID NOT COMPILE')
              elif result.returncode == 0:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO (%d)' % result.returncode)
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h)
          return TryRunResultHolder(result)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.compiler.get_id()
  
      @noPosargs
      @permittedKwargs({})
      @FeatureNew('compiler.get_linker_id', '0.53.0')
      def get_linker_id_method(self, args, kwargs):
          return self.compiler.get_linker_id()
  
      @noPosargs
      @permittedKwargs({})
      def symbols_have_underscore_prefix_method(self, args, kwargs):
          '''
          Check if the compiler prefixes _ (underscore) to global C symbols
          See: https://en.wikipedia.org/wiki/Name_mangling#C
          '''
          return self.compiler.symbols_have_underscore_prefix(self.environment)
  
      @noPosargs
      @permittedKwargs({})
      def unittest_args_method(self, args, kwargs):
          '''
          This function is deprecated and should not be used.
          It can be removed in a future version of Meson.
          '''
          if not hasattr(self.compiler, 'get_feature_args'):
              raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language()))
          build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir())
          return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src)
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_member_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Has_member takes exactly two arguments.')
          check_stringlist(args)
          typename, membername = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_member must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_members(typename, [membername], prefix,
                                                  self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking whether type', mlog.bold(typename, True),
                   'has member', mlog.bold(membername, True), msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_members_method(self, args, kwargs):
          if len(args) < 2:
              raise InterpreterException('Has_members needs at least two arguments.')
          check_stringlist(args)
          typename, *membernames = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_members must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_members(typename, membernames, prefix,
                                                  self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          members = mlog.bold(', '.join(['"{}"'.format(m) for m in membernames]))
          mlog.log('Checking whether type', mlog.bold(typename, True),
                   'has members', members, msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_function_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Has_function takes exactly one argument.')
          check_stringlist(args)
          funcname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_function must be a string.')
          extra_args = self.determine_args(kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_function(funcname, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking for function', mlog.bold(funcname, True), msg, hadtxt, cached)
          return had
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def has_type_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Has_type takes exactly one argument.')
          check_stringlist(args)
          typename = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_type must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          had, cached = self.compiler.has_type(typename, prefix, self.environment,
                                               extra_args=extra_args, dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if had:
              hadtxt = mlog.green('YES')
          else:
              hadtxt = mlog.red('NO')
          mlog.log('Checking for type', mlog.bold(typename, True), msg, hadtxt, cached)
          return had
  
      @FeatureNew('compiler.compute_int', '0.40.0')
      @permittedKwargs({
          'prefix',
          'low',
          'high',
          'guess',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def compute_int_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Compute_int takes exactly one argument.')
          check_stringlist(args)
          expression = args[0]
          prefix = kwargs.get('prefix', '')
          low = kwargs.get('low', None)
          high = kwargs.get('high', None)
          guess = kwargs.get('guess', None)
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of compute_int must be a string.')
          if low is not None and not isinstance(low, int):
              raise InterpreterException('Low argument of compute_int must be an int.')
          if high is not None and not isinstance(high, int):
              raise InterpreterException('High argument of compute_int must be an int.')
          if guess is not None and not isinstance(guess, int):
              raise InterpreterException('Guess argument of compute_int must be an int.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          res = self.compiler.compute_int(expression, low, high, guess, prefix,
                                          self.environment, extra_args=extra_args,
                                          dependencies=deps)
          mlog.log('Computing int of', mlog.bold(expression, True), msg, res)
          return res
  
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def sizeof_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Sizeof takes exactly one argument.')
          check_stringlist(args)
          element = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of sizeof must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          esize = self.compiler.sizeof(element, prefix, self.environment,
                                       extra_args=extra_args, dependencies=deps)
          mlog.log('Checking for size of', mlog.bold(element, True), msg, esize)
          return esize
  
      @FeatureNew('compiler.get_define', '0.40.0')
      @permittedKwargs({
          'prefix',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def get_define_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('get_define() takes exactly one argument.')
          check_stringlist(args)
          element = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of get_define() must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          value, cached = self.compiler.get_define(element, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached)
          return value
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def compiles_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('compiles method takes exactly one argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result, cached = self.compiler.compiles(code, self.environment,
                                                  extra_args=extra_args,
                                                  dependencies=deps)
          if len(testname) > 0:
              if result:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO')
              cached = mlog.blue('(cached)') if cached else ''
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'compiles:', h, cached)
          return result
  
      @permittedKwargs({
          'name',
          'no_builtin_args',
          'include_directories',
          'args',
          'dependencies',
      })
      def links_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('links method takes exactly one argument.')
          code = args[0]
          if isinstance(code, mesonlib.File):
              code = mesonlib.File.from_absolute_file(
                  code.rel_to_builddir(self.environment.source_dir))
          elif not isinstance(code, str):
              raise InvalidArguments('Argument must be string or file.')
          testname = kwargs.get('name', '')
          if not isinstance(testname, str):
              raise InterpreterException('Testname argument must be a string.')
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs, endl=None)
          result, cached = self.compiler.links(code, self.environment,
                                               extra_args=extra_args,
                                               dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if len(testname) > 0:
              if result:
                  h = mlog.green('YES')
              else:
                  h = mlog.red('NO')
              mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached)
          return result
  
      @FeatureNew('compiler.check_header', '0.47.0')
      @FeatureNewKwargs('compiler.check_header', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def check_header_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('check_header method takes exactly one argument.')
          check_stringlist(args)
          hname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.check_header(hname, prefix, self.environment,
                                                   extra_args=extra_args,
                                                   dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if required and not haz:
              raise InterpreterException('{} header {!r} not usable'.format(self.compiler.get_display_language(), hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log('Check usable header', mlog.bold(hname, True), msg, h, cached)
          return haz
  
      @FeatureNewKwargs('compiler.has_header', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def has_header_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('has_header method takes exactly one argument.')
          check_stringlist(args)
          hname = args[0]
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.has_header(hname, prefix, self.environment,
                                                 extra_args=extra_args, dependencies=deps)
          cached = mlog.blue('(cached)') if cached else ''
          if required and not haz:
              raise InterpreterException('{} header {!r} not found'.format(self.compiler.get_display_language(), hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log('Has header', mlog.bold(hname, True), msg, h, cached)
          return haz
  
      @FeatureNewKwargs('compiler.has_header_symbol', '0.50.0', ['required'])
      @permittedKwargs(header_permitted_kwargs)
      def has_header_symbol_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('has_header_symbol method takes exactly two arguments.')
          check_stringlist(args)
          hname, symbol = args
          prefix = kwargs.get('prefix', '')
          if not isinstance(prefix, str):
              raise InterpreterException('Prefix argument of has_header_symbol must be a string.')
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
          if disabled:
              mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          extra_args = functools.partial(self.determine_args, kwargs)
          deps, msg = self.determine_dependencies(kwargs)
          haz, cached = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment,
                                                        extra_args=extra_args,
                                                        dependencies=deps)
          if required and not haz:
              raise InterpreterException('{} symbol {} not found in header {}'.format(self.compiler.get_display_language(), symbol, hname))
          elif haz:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), msg, h, cached)
          return haz
  
      def notfound_library(self, libname):
          lib = dependencies.ExternalLibrary(libname, None,
                                             self.environment,
                                             self.compiler.language,
                                             silent=True)
          return ExternalLibraryHolder(lib, self.subproject)
  
      @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static'])
      @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers'])
      @FeatureNewKwargs('compiler.find_library', '0.49.0', ['disabler'])
      @disablerIfNotFound
      @permittedKwargs(find_library_permitted_kwargs)
      def find_library_method(self, args, kwargs):
          # TODO add dependencies support?
          if len(args) != 1:
              raise InterpreterException('find_library method takes one argument.')
          libname = args[0]
          if not isinstance(libname, str):
              raise InterpreterException('Library name not a string.')
  
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.notfound_library(libname)
  
          has_header_kwargs = {k[7:]: v for k, v in kwargs.items() if k.startswith('header_')}
          has_header_kwargs['required'] = required
          headers = mesonlib.stringlistify(kwargs.get('has_headers', []))
          for h in headers:
              if not self.has_header_method([h], has_header_kwargs):
                  return self.notfound_library(libname)
  
          search_dirs = extract_search_dirs(kwargs)
  
          libtype = mesonlib.LibType.PREFER_SHARED
          if 'static' in kwargs:
              if not isinstance(kwargs['static'], bool):
                  raise InterpreterException('static must be a boolean')
              libtype = mesonlib.LibType.STATIC if kwargs['static'] else mesonlib.LibType.SHARED
          linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype)
          if required and not linkargs:
              raise InterpreterException(
                  '{} library {!r} not found'.format(self.compiler.get_display_language(), libname))
          lib = dependencies.ExternalLibrary(libname, linkargs, self.environment,
                                             self.compiler.language)
          return ExternalLibraryHolder(lib, self.subproject)
  
      @permittedKwargs({})
      def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool:
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_argument takes exactly one argument.')
          return self.has_multi_arguments_method(args, kwargs)
  
      @permittedKwargs({})
      def has_multi_arguments_method(self, args: T.Sequence[str], kwargs: dict):
          args = mesonlib.stringlistify(args)
          result, cached = self.compiler.has_multi_arguments(args, self.environment)
          if result:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          cached = mlog.blue('(cached)') if cached else ''
          mlog.log(
              'Compiler for {} supports arguments {}:'.format(
                  self.compiler.get_display_language(), ' '.join(args)),
              h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_arguments', '0.43.0')
      @permittedKwargs({})
      def get_supported_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          supported_args = []
          for arg in args:
              if self.has_argument_method(arg, kwargs):
                  supported_args.append(arg)
          return supported_args
  
      @permittedKwargs({})
      def first_supported_argument_method(self, args: T.Sequence[str], kwargs: dict) -> T.List[str]:
          for arg in mesonlib.stringlistify(args):
              if self.has_argument_method(arg, kwargs):
                  mlog.log('First supported argument:', mlog.bold(arg))
                  return [arg]
          mlog.log('First supported argument:', mlog.red('None'))
          return []
  
      @FeatureNew('compiler.has_link_argument', '0.46.0')
      @permittedKwargs({})
      def has_link_argument_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_link_argument takes exactly one argument.')
          return self.has_multi_link_arguments_method(args, kwargs)
  
      @FeatureNew('compiler.has_multi_link_argument', '0.46.0')
      @permittedKwargs({})
      def has_multi_link_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          result, cached = self.compiler.has_multi_link_arguments(args, self.environment)
          cached = mlog.blue('(cached)') if cached else ''
          if result:
              h = mlog.green('YES')
          else:
              h = mlog.red('NO')
          mlog.log(
              'Compiler for {} supports link arguments {}:'.format(
                  self.compiler.get_display_language(), ' '.join(args)),
              h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_link_arguments_method', '0.46.0')
      @permittedKwargs({})
      def get_supported_link_arguments_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          supported_args = []
          for arg in args:
              if self.has_link_argument_method(arg, kwargs):
                  supported_args.append(arg)
          return supported_args
  
      @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0')
      @permittedKwargs({})
      def first_supported_link_argument_method(self, args, kwargs):
          for i in mesonlib.stringlistify(args):
              if self.has_link_argument_method(i, kwargs):
                  mlog.log('First supported link argument:', mlog.bold(i))
                  return [i]
          mlog.log('First supported link argument:', mlog.red('None'))
          return []
  
      @FeatureNew('compiler.has_function_attribute', '0.48.0')
      @permittedKwargs({})
      def has_func_attribute_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          if len(args) != 1:
              raise InterpreterException('has_func_attribute takes exactly one argument.')
          result, cached = self.compiler.has_func_attribute(args[0], self.environment)
          cached = mlog.blue('(cached)') if cached else ''
          h = mlog.green('YES') if result else mlog.red('NO')
          mlog.log('Compiler for {} supports function attribute {}:'.format(self.compiler.get_display_language(), args[0]), h, cached)
          return result
  
      @FeatureNew('compiler.get_supported_function_attributes', '0.48.0')
      @permittedKwargs({})
      def get_supported_function_attributes_method(self, args, kwargs):
          args = mesonlib.stringlistify(args)
          return [a for a in args if self.has_func_attribute_method(a, kwargs)]
  
      @FeatureNew('compiler.get_argument_syntax_method', '0.49.0')
      @noPosargs
      @noKwargs
      def get_argument_syntax_method(self, args, kwargs):
          return self.compiler.get_argument_syntax()
  
  
  ModuleState = collections.namedtuple('ModuleState', [
      'source_root', 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment',
      'project_name', 'project_version', 'backend', 'targets',
      'data', 'headers', 'man', 'global_args', 'project_args', 'build_machine',
      'host_machine', 'target_machine', 'current_node'])
  
  class ModuleHolder(InterpreterObject, ObjectHolder):
      def __init__(self, modname, module, interpreter):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, module)
          self.modname = modname
          self.interpreter = interpreter
  
      def method_call(self, method_name, args, kwargs):
          try:
              fn = getattr(self.held_object, method_name)
          except AttributeError:
              raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name))
          if method_name.startswith('_'):
              raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname))
          if not getattr(fn, 'no-args-flattening', False):
              args = flatten(args)
          # This is not 100% reliable but we can't use hash()
          # because the Build object contains dicts and lists.
          num_targets = len(self.interpreter.build.targets)
          state = ModuleState(
              source_root = self.interpreter.environment.get_source_dir(),
              build_to_src=mesonlib.relpath(self.interpreter.environment.get_source_dir(),
                                            self.interpreter.environment.get_build_dir()),
              subproject=self.interpreter.subproject,
              subdir=self.interpreter.subdir,
              current_lineno=self.interpreter.current_lineno,
              environment=self.interpreter.environment,
              project_name=self.interpreter.build.project_name,
              project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname],
              # The backend object is under-used right now, but we will need it:
              # https://github.com/mesonbuild/meson/issues/1419
              backend=self.interpreter.backend,
              targets=self.interpreter.build.targets,
              data=self.interpreter.build.data,
              headers=self.interpreter.build.get_headers(),
              man=self.interpreter.build.get_man(),
              #global_args_for_build = self.interpreter.build.global_args.build,
              global_args = self.interpreter.build.global_args.host,
              #project_args_for_build = self.interpreter.build.projects_args.build.get(self.interpreter.subproject, {}),
              project_args = self.interpreter.build.projects_args.host.get(self.interpreter.subproject, {}),
              build_machine=self.interpreter.builtin['build_machine'].held_object,
              host_machine=self.interpreter.builtin['host_machine'].held_object,
              target_machine=self.interpreter.builtin['target_machine'].held_object,
              current_node=self.current_node
          )
          # Many modules do for example self.interpreter.find_program_impl(),
          # so we have to ensure they use the current interpreter and not the one
          # that first imported that module, otherwise it will use outdated
          # overrides.
          self.held_object.interpreter = self.interpreter
          if self.held_object.is_snippet(method_name):
              value = fn(self.interpreter, state, args, kwargs)
              return self.interpreter.holderify(value)
          else:
              value = fn(state, args, kwargs)
              if num_targets != len(self.interpreter.build.targets):
                  raise InterpreterException('Extension module altered internal state illegally.')
              return self.interpreter.module_method_callback(value)
  
  
  class Summary:
      def __init__(self, project_name, project_version):
          self.project_name = project_name
          self.project_version = project_version
          self.sections = collections.defaultdict(dict)
          self.max_key_len = 0
  
      def add_section(self, section, values, kwargs):
          bool_yn = kwargs.get('bool_yn', False)
          if not isinstance(bool_yn, bool):
              raise InterpreterException('bool_yn keyword argument must be boolean')
          list_sep = kwargs.get('list_sep')
          if list_sep is not None and not isinstance(list_sep, str):
              raise InterpreterException('list_sep keyword argument must be string')
          for k, v in values.items():
              if k in self.sections[section]:
                  raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k))
              formatted_values = []
              for i in listify(v):
                  if not isinstance(i, (str, int)):
                      m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean'
                      raise InterpreterException(m.format(section, k))
                  if bool_yn and isinstance(i, bool):
                      formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
                  else:
                      formatted_values.append(i)
              self.sections[section][k] = (formatted_values, list_sep)
              self.max_key_len = max(self.max_key_len, len(k))
  
      def dump(self):
          mlog.log(self.project_name, mlog.normal_cyan(self.project_version))
          for section, values in self.sections.items():
              mlog.log('')  # newline
              if section:
                  mlog.log(' ', mlog.bold(section))
              for k, v in values.items():
                  v, list_sep = v
                  indent = self.max_key_len - len(k) + 3
                  end = ' ' if v else ''
                  mlog.log(' ' * indent, k + ':', end=end)
                  if list_sep is None:
                      indent = self.max_key_len + 6
                      list_sep = '\n' + ' ' * indent
                  mlog.log(*v, sep=list_sep)
          mlog.log('')  # newline
  
  
  class MesonMain(InterpreterObject):
      def __init__(self, build, interpreter):
          InterpreterObject.__init__(self)
          self.build = build
          self.interpreter = interpreter
          self._found_source_scripts = {}
          self.methods.update({'get_compiler': self.get_compiler_method,
                               'is_cross_build': self.is_cross_build_method,
                               'has_exe_wrapper': self.has_exe_wrapper_method,
                               'is_unity': self.is_unity_method,
                               'is_subproject': self.is_subproject_method,
                               'current_source_dir': self.current_source_dir_method,
                               'current_build_dir': self.current_build_dir_method,
                               'source_root': self.source_root_method,
                               'build_root': self.build_root_method,
                               'add_install_script': self.add_install_script_method,
                               'add_postconf_script': self.add_postconf_script_method,
                               'add_dist_script': self.add_dist_script_method,
                               'install_dependency_manifest': self.install_dependency_manifest_method,
                               'override_dependency': self.override_dependency_method,
                               'override_find_program': self.override_find_program_method,
                               'project_version': self.project_version_method,
                               'project_license': self.project_license_method,
                               'version': self.version_method,
                               'project_name': self.project_name_method,
                               'get_cross_property': self.get_cross_property_method,
                               'get_external_property': self.get_external_property_method,
                               'backend': self.backend_method,
                               })
  
      def _find_source_script(self, name, args):
          # Prefer scripts in the current source directory
          search_dir = os.path.join(self.interpreter.environment.source_dir,
                                    self.interpreter.subdir)
          key = (name, search_dir)
          if key in self._found_source_scripts:
              found = self._found_source_scripts[key]
          else:
              found = dependencies.ExternalProgram(name, search_dir=search_dir)
              if found.found():
                  self._found_source_scripts[key] = found
              else:
                  m = 'Script or command {!r} not found or not executable'
                  raise InterpreterException(m.format(name))
          return build.RunScript(found.get_command(), args)
  
      @permittedKwargs({})
      def add_install_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_install_script takes one or more arguments')
          check_stringlist(args, 'add_install_script args must be strings')
          script = self._find_source_script(args[0], args[1:])
          self.build.install_scripts.append(script)
  
      @permittedKwargs({})
      def add_postconf_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_postconf_script takes one or more arguments')
          check_stringlist(args, 'add_postconf_script arguments must be strings')
          script = self._find_source_script(args[0], args[1:])
          self.build.postconf_scripts.append(script)
  
      @permittedKwargs({})
      def add_dist_script_method(self, args, kwargs):
          if len(args) < 1:
              raise InterpreterException('add_dist_script takes one or more arguments')
          if len(args) > 1:
              FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject)
          check_stringlist(args, 'add_dist_script argument must be a string')
          if self.interpreter.subproject != '':
              raise InterpreterException('add_dist_script may not be used in a subproject.')
          script = self._find_source_script(args[0], args[1:])
          self.build.dist_scripts.append(script)
  
      @noPosargs
      @permittedKwargs({})
      def current_source_dir_method(self, args, kwargs):
          src = self.interpreter.environment.source_dir
          sub = self.interpreter.subdir
          if sub == '':
              return src
          return os.path.join(src, sub)
  
      @noPosargs
      @permittedKwargs({})
      def current_build_dir_method(self, args, kwargs):
          src = self.interpreter.environment.build_dir
          sub = self.interpreter.subdir
          if sub == '':
              return src
          return os.path.join(src, sub)
  
      @noPosargs
      @permittedKwargs({})
      def backend_method(self, args, kwargs):
          return self.interpreter.backend.name
  
      @noPosargs
      @permittedKwargs({})
      def source_root_method(self, args, kwargs):
          return self.interpreter.environment.source_dir
  
      @noPosargs
      @permittedKwargs({})
      def build_root_method(self, args, kwargs):
          return self.interpreter.environment.build_dir
  
      @noPosargs
      @permittedKwargs({})
      def has_exe_wrapper_method(self, args, kwargs):
          if self.is_cross_build_method(None, None) and \
             self.build.environment.need_exe_wrapper():
              if self.build.environment.exe_wrapper is None:
                  return False
          # We return True when exe_wrap is defined, when it's not needed, and
          # when we're compiling natively. The last two are semantically confusing.
          # Need to revisit this.
          return True
  
      @noPosargs
      @permittedKwargs({})
      def is_cross_build_method(self, args, kwargs):
          return self.build.environment.is_cross_build()
  
      @permittedKwargs({'native'})
      def get_compiler_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('get_compiler_method must have one and only one argument.')
          cname = args[0]
          for_machine = Interpreter.machine_from_native_kwarg(kwargs)
          clist = self.interpreter.coredata.compilers[for_machine]
          if cname in clist:
              return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject)
          raise InterpreterException('Tried to access compiler for unspecified language "%s".' % cname)
  
      @noPosargs
      @permittedKwargs({})
      def is_unity_method(self, args, kwargs):
          optval = self.interpreter.environment.coredata.get_builtin_option('unity')
          if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()):
              return True
          return False
  
      @noPosargs
      @permittedKwargs({})
      def is_subproject_method(self, args, kwargs):
          return self.interpreter.is_subproject()
  
      @permittedKwargs({})
      def install_dependency_manifest_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Must specify manifest install file name')
          if not isinstance(args[0], str):
              raise InterpreterException('Argument must be a string.')
          self.build.dep_manifest_name = args[0]
  
      @FeatureNew('meson.override_find_program', '0.46.0')
      @permittedKwargs({})
      def override_find_program_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Override needs two arguments')
          name, exe = args
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string')
          if hasattr(exe, 'held_object'):
              exe = exe.held_object
          if isinstance(exe, mesonlib.File):
              abspath = exe.absolute_path(self.interpreter.environment.source_dir,
                                          self.interpreter.environment.build_dir)
              if not os.path.exists(abspath):
                  raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
              exe = OverrideProgram(abspath)
          if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)):
              raise InterpreterException('Second argument must be an external program or executable.')
          self.interpreter.add_find_program_override(name, exe)
  
      @FeatureNew('meson.override_dependency', '0.54.0')
      @permittedKwargs({'native'})
      def override_dependency_method(self, args, kwargs):
          if len(args) != 2:
              raise InterpreterException('Override needs two arguments')
          name = args[0]
          dep = args[1]
          if not isinstance(name, str) or not name:
              raise InterpreterException('First argument must be a string and cannot be empty')
          if hasattr(dep, 'held_object'):
              dep = dep.held_object
          if not isinstance(dep, dependencies.Dependency):
              raise InterpreterException('Second argument must be a dependency object')
          identifier = dependencies.get_dep_identifier(name, kwargs)
          for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
          override = self.build.dependency_overrides[for_machine].get(identifier)
          if override:
              m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}'
              location = mlog.get_error_location_string(override.node.filename, override.node.lineno)
              raise InterpreterException(m.format(name, location))
          self.build.dependency_overrides[for_machine][identifier] = \
              build.DependencyOverride(dep, self.interpreter.current_node)
  
      @noPosargs
      @permittedKwargs({})
      def project_version_method(self, args, kwargs):
          return self.build.dep_manifest[self.interpreter.active_projectname]['version']
  
      @FeatureNew('meson.project_license()', '0.45.0')
      @noPosargs
      @permittedKwargs({})
      def project_license_method(self, args, kwargs):
          return self.build.dep_manifest[self.interpreter.active_projectname]['license']
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return coredata.version
  
      @noPosargs
      @permittedKwargs({})
      def project_name_method(self, args, kwargs):
          return self.interpreter.active_projectname
  
      @noArgsFlattening
      @permittedKwargs({})
      def get_cross_property_method(self, args, kwargs) -> str:
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Must have one or two arguments.')
          propname = args[0]
          if not isinstance(propname, str):
              raise InterpreterException('Property name must be string.')
          try:
              props = self.interpreter.environment.properties.host
              return props[propname]
          except Exception:
              if len(args) == 2:
                  return args[1]
              raise InterpreterException('Unknown cross property: %s.' % propname)
  
      @noArgsFlattening
      @permittedKwargs({'native'})
      @FeatureNew('meson.get_external_property', '0.54.0')
      def get_external_property_method(self, args: T.Sequence[str], kwargs: dict) -> str:
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Must have one or two positional arguments.')
          propname = args[0]
          if not isinstance(propname, str):
              raise InterpreterException('Property name must be string.')
  
          def _get_native() -> str:
              try:
                  props = self.interpreter.environment.properties.build
                  return props[propname]
              except Exception:
                  if len(args) == 2:
                      return args[1]
                  raise InterpreterException('Unknown native property: %s.' % propname)
          if 'native' in kwargs:
              if kwargs['native']:
                  return _get_native()
              else:
                  return self.get_cross_property_method(args, {})
          else:  # native: not specified
              if self.build.environment.is_cross_build():
                  return self.get_cross_property_method(args, kwargs)
              else:
                  return _get_native()
  
  known_library_kwargs = (
      build.known_shlib_kwargs |
      build.known_stlib_kwargs
  )
  
  known_build_target_kwargs = (
      known_library_kwargs |
      build.known_exe_kwargs |
      build.known_jar_kwargs |
      {'target_type'}
  )
  
  _base_test_args = {'args', 'depends', 'env', 'should_fail', 'timeout', 'workdir', 'suite', 'priority', 'protocol'}
  
  permitted_kwargs = {'add_global_arguments': {'language', 'native'},
                      'add_global_link_arguments': {'language', 'native'},
                      'add_languages': {'required', 'native'},
                      'add_project_link_arguments': {'language', 'native'},
                      'add_project_arguments': {'language', 'native'},
                      'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default'},
                      'benchmark': _base_test_args,
                      'build_target': known_build_target_kwargs,
                      'configure_file': {'input',
                                         'output',
                                         'configuration',
                                         'command',
                                         'copy',
                                         'depfile',
                                         'install_dir',
                                         'install_mode',
                                         'capture',
                                         'install',
                                         'format',
                                         'output_format',
                                         'encoding'},
                      'custom_target': {'input',
                                        'output',
                                        'command',
                                        'install',
                                        'install_dir',
                                        'install_mode',
                                        'build_always',
                                        'capture',
                                        'depends',
                                        'depend_files',
                                        'depfile',
                                        'build_by_default',
                                        'build_always_stale',
                                        'console'},
                      'dependency': {'default_options',
                                     'embed',
                                     'fallback',
                                     'language',
                                     'main',
                                     'method',
                                     'modules',
                                     'components',
                                     'cmake_module_path',
                                     'optional_modules',
                                     'native',
                                     'not_found_message',
                                     'required',
                                     'static',
                                     'version',
                                     'private_headers',
                                     'cmake_args',
                                     'include_type',
                                     },
                      'declare_dependency': {'include_directories',
                                             'link_with',
                                             'sources',
                                             'dependencies',
                                             'compile_args',
                                             'link_args',
                                             'link_whole',
                                             'version',
                                             'variables',
                                             },
                      'executable': build.known_exe_kwargs,
                      'find_program': {'required', 'native', 'version', 'dirs'},
                      'generator': {'arguments',
                                    'output',
                                    'depends',
                                    'depfile',
                                    'capture',
                                    'preserve_path_from'},
                      'include_directories': {'is_system'},
                      'install_data': {'install_dir', 'install_mode', 'rename', 'sources'},
                      'install_headers': {'install_dir', 'install_mode', 'subdir'},
                      'install_man': {'install_dir', 'install_mode'},
                      'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'},
                      'jar': build.known_jar_kwargs,
                      'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
                      'run_command': {'check', 'capture', 'env'},
                      'run_target': {'command', 'depends'},
                      'shared_library': build.known_shlib_kwargs,
                      'shared_module': build.known_shmod_kwargs,
                      'static_library': build.known_stlib_kwargs,
                      'both_libraries': known_library_kwargs,
                      'library': known_library_kwargs,
                      'subdir': {'if_found'},
                      'subproject': {'version', 'default_options', 'required'},
                      'test': set.union(_base_test_args, {'is_parallel'}),
                      'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'},
                      }
  
  
  class Interpreter(InterpreterBase):
  
      def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects',
                   modules = None, default_project_options=None, mock=False, ast=None):
          super().__init__(build.environment.get_source_dir(), subdir, subproject)
          self.an_unpicklable_object = mesonlib.an_unpicklable_object
          self.build = build
          self.environment = build.environment
          self.coredata = self.environment.get_coredata()
          self.backend = backend
          self.summary = {}
          if modules is None:
              self.modules = {}
          else:
              self.modules = modules
          # Subproject directory is usually the name of the subproject, but can
          # be different for dependencies provided by wrap files.
          self.subproject_directory_name = subdir.split(os.path.sep)[-1]
          self.subproject_dir = subproject_dir
          self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
          if not mock and ast is None:
              self.load_root_meson_file()
              self.sanity_check_ast()
          elif ast is not None:
              self.ast = ast
              self.sanity_check_ast()
          self.builtin.update({'meson': MesonMain(build, self)})
          self.generators = []
          self.visited_subdirs = {}
          self.project_args_frozen = False
          self.global_args_frozen = False  # implies self.project_args_frozen
          self.subprojects = {}
          self.subproject_stack = []
          self.configure_file_outputs = {}
          # Passed from the outside, only used in subprojects.
          if default_project_options:
              self.default_project_options = default_project_options.copy()
          else:
              self.default_project_options = {}
          self.project_default_options = {}
          self.build_func_dict()
          # build_def_files needs to be defined before parse_project is called
          self.build_def_files = [os.path.join(self.subdir, environment.build_filename)]
          if not mock:
              self.parse_project()
          self._redetect_machines()
  
      def _redetect_machines(self):
          # Re-initialize machine descriptions. We can do a better job now because we
          # have the compilers needed to gain more knowledge, so wipe out old
          # inference and start over.
          machines = self.build.environment.machines.miss_defaulting()
          machines.build = environment.detect_machine_info(self.coredata.compilers.build)
          self.build.environment.machines = machines.default_missing()
          assert self.build.environment.machines.build.cpu is not None
          assert self.build.environment.machines.host.cpu is not None
          assert self.build.environment.machines.target.cpu is not None
  
          self.builtin['build_machine'] = \
              MachineHolder(self.build.environment.machines.build)
          self.builtin['host_machine'] = \
              MachineHolder(self.build.environment.machines.host)
          self.builtin['target_machine'] = \
              MachineHolder(self.build.environment.machines.target)
  
      def get_non_matching_default_options(self):
          env = self.environment
          for def_opt_name, def_opt_value in self.project_default_options.items():
              for opts in env.coredata.get_all_options():
                  cur_opt_value = opts.get(def_opt_name)
                  if cur_opt_value is not None:
                      def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value)
                      if def_opt_value != cur_opt_value.value:
                          yield (def_opt_name, def_opt_value, cur_opt_value)
  
      def build_func_dict(self):
          self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
                             'add_project_arguments': self.func_add_project_arguments,
                             'add_global_link_arguments': self.func_add_global_link_arguments,
                             'add_project_link_arguments': self.func_add_project_link_arguments,
                             'add_test_setup': self.func_add_test_setup,
                             'add_languages': self.func_add_languages,
                             'alias_target': self.func_alias_target,
                             'assert': self.func_assert,
                             'benchmark': self.func_benchmark,
                             'build_target': self.func_build_target,
                             'configuration_data': self.func_configuration_data,
                             'configure_file': self.func_configure_file,
                             'custom_target': self.func_custom_target,
                             'declare_dependency': self.func_declare_dependency,
                             'dependency': self.func_dependency,
                             'disabler': self.func_disabler,
                             'environment': self.func_environment,
                             'error': self.func_error,
                             'executable': self.func_executable,
                             'generator': self.func_generator,
                             'gettext': self.func_gettext,
                             'get_option': self.func_get_option,
                             'get_variable': self.func_get_variable,
                             'files': self.func_files,
                             'find_library': self.func_find_library,
                             'find_program': self.func_find_program,
                             'include_directories': self.func_include_directories,
                             'import': self.func_import,
                             'install_data': self.func_install_data,
                             'install_headers': self.func_install_headers,
                             'install_man': self.func_install_man,
                             'install_subdir': self.func_install_subdir,
                             'is_disabler': self.func_is_disabler,
                             'is_variable': self.func_is_variable,
                             'jar': self.func_jar,
                             'join_paths': self.func_join_paths,
                             'library': self.func_library,
                             'message': self.func_message,
                             'warning': self.func_warning,
                             'option': self.func_option,
                             'project': self.func_project,
                             'run_target': self.func_run_target,
                             'run_command': self.func_run_command,
                             'set_variable': self.func_set_variable,
                             'subdir': self.func_subdir,
                             'subdir_done': self.func_subdir_done,
                             'subproject': self.func_subproject,
                             'summary': self.func_summary,
                             'shared_library': self.func_shared_lib,
                             'shared_module': self.func_shared_module,
                             'static_library': self.func_static_lib,
                             'both_libraries': self.func_both_lib,
                             'test': self.func_test,
                             'vcs_tag': self.func_vcs_tag
                             })
          if 'MESON_UNIT_TEST' in os.environ:
              self.funcs.update({'exception': self.func_exception})
  
      def holderify(self, item):
          if isinstance(item, list):
              return [self.holderify(x) for x in item]
          if isinstance(item, dict):
              return {k: self.holderify(v) for k, v in item.items()}
  
          if isinstance(item, build.CustomTarget):
              return CustomTargetHolder(item, self)
          elif isinstance(item, (int, str, bool, Disabler)) or item is None:
              return item
          elif isinstance(item, build.Executable):
              return ExecutableHolder(item, self)
          elif isinstance(item, build.GeneratedList):
              return GeneratedListHolder(item)
          elif isinstance(item, build.RunTarget):
              raise RuntimeError('This is not a pipe.')
          elif isinstance(item, build.RunScript):
              raise RuntimeError('Do not do this.')
          elif isinstance(item, build.Data):
              return DataHolder(item)
          elif isinstance(item, dependencies.Dependency):
              return DependencyHolder(item, self.subproject)
          elif isinstance(item, dependencies.ExternalProgram):
              return ExternalProgramHolder(item, self.subproject)
          elif hasattr(item, 'held_object'):
              return item
          else:
              raise InterpreterException('Module returned a value of unknown type.')
  
      def process_new_values(self, invalues):
          invalues = listify(invalues)
          for v in invalues:
              if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)):
                  v = v.held_object
  
              if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)):
                  self.add_target(v.name, v)
              elif isinstance(v, list):
                  self.module_method_callback(v)
              elif isinstance(v, build.GeneratedList):
                  pass
              elif isinstance(v, build.RunScript):
                  self.build.install_scripts.append(v)
              elif isinstance(v, build.Data):
                  self.build.data.append(v)
              elif isinstance(v, dependencies.ExternalProgram):
                  return ExternalProgramHolder(v, self.subproject)
              elif isinstance(v, dependencies.InternalDependency):
                  # FIXME: This is special cased and not ideal:
                  # The first source is our new VapiTarget, the rest are deps
                  self.process_new_values(v.sources[0])
              elif hasattr(v, 'held_object'):
                  pass
              elif isinstance(v, (int, str, bool, Disabler)):
                  pass
              else:
                  raise InterpreterException('Module returned a value of unknown type.')
  
      def module_method_callback(self, return_object):
          if not isinstance(return_object, ModuleReturnValue):
              raise InterpreterException('Bug in module, it returned an invalid object')
          invalues = return_object.new_objects
          self.process_new_values(invalues)
          return self.holderify(return_object.return_value)
  
      def get_build_def_files(self):
          return self.build_def_files
  
      def add_build_def_file(self, f):
          # Use relative path for files within source directory, and absolute path
          # for system files. Skip files within build directory. Also skip not regular
          # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this
          # is especially important to convert '/' to '\' on Windows.
          if isinstance(f, mesonlib.File):
              if f.is_built:
                  return
              f = os.path.normpath(f.relative_name())
          elif os.path.isfile(f) and not f.startswith('/dev'):
              srcdir = Path(self.environment.get_source_dir())
              builddir = Path(self.environment.get_build_dir())
              f = Path(f).resolve()
              if builddir in f.parents:
                  return
              if srcdir in f.parents:
                  f = f.relative_to(srcdir)
              f = str(f)
          else:
              return
          if f not in self.build_def_files:
              self.build_def_files.append(f)
  
      def get_variables(self):
          return self.variables
  
      def check_stdlibs(self):
          for for_machine in MachineChoice:
              props = self.build.environment.properties[for_machine]
              for l in self.coredata.compilers[for_machine].keys():
                  try:
                      di = mesonlib.stringlistify(props.get_stdlib(l))
                      if len(di) != 2:
                          raise InterpreterException('Stdlib definition for %s should have exactly two elements.'
                                                     % l)
                      projname, depname = di
                      subproj = self.do_subproject(projname, 'meson', {})
                      self.build.stdlibs.host[l] = subproj.get_variable_method([depname], {})
                  except KeyError:
                      pass
                  except InvalidArguments:
                      pass
  
      @stringArgs
      @noKwargs
      def func_import(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Import takes one argument.')
          modname = args[0]
          if modname.startswith('unstable-'):
              plainname = modname.split('-', 1)[1]
              mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node)
              modname = 'unstable_' + plainname
          if modname not in self.modules:
              try:
                  module = importlib.import_module('mesonbuild.modules.' + modname)
              except ImportError:
                  raise InvalidArguments('Module "%s" does not exist' % (modname, ))
              self.modules[modname] = module.initialize(self)
          return ModuleHolder(modname, self.modules[modname], self)
  
      @stringArgs
      @noKwargs
      def func_files(self, node, args, kwargs):
          return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args]
  
      @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole'])
      @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables'])
      @permittedKwargs(permitted_kwargs['declare_dependency'])
      @noPosargs
      def func_declare_dependency(self, node, args, kwargs):
          version = kwargs.get('version', self.project_version)
          if not isinstance(version, str):
              raise InterpreterException('Version must be a string.')
          incs = self.extract_incdirs(kwargs)
          libs = unholder(extract_as_list(kwargs, 'link_with'))
          libs_whole = unholder(extract_as_list(kwargs, 'link_whole'))
          sources = extract_as_list(kwargs, 'sources')
          sources = unholder(listify(self.source_strings_to_files(sources)))
          deps = unholder(extract_as_list(kwargs, 'dependencies'))
          compile_args = mesonlib.stringlistify(kwargs.get('compile_args', []))
          link_args = mesonlib.stringlistify(kwargs.get('link_args', []))
          variables = kwargs.get('variables', {})
          if not isinstance(variables, dict):
              raise InterpreterException('variables must be a dict.')
          if not all(isinstance(v, str) for v in variables.values()):
              # Because that is how they will come from pkg-config and cmake
              raise InterpreterException('variables values be strings.')
          final_deps = []
          for d in deps:
              try:
                  d = d.held_object
              except Exception:
                  pass
              if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)):
                  raise InterpreterException('Dependencies must be external deps')
              final_deps.append(d)
          for l in libs:
              if isinstance(l, dependencies.Dependency):
                  raise InterpreterException('''Entries in "link_with" may only be self-built targets,
  external dependencies (including libraries) must go to "dependencies".''')
          dep = dependencies.InternalDependency(version, incs, compile_args,
                                                link_args, libs, libs_whole, sources, final_deps,
                                                variables)
          return DependencyHolder(dep, self.subproject)
  
      @noKwargs
      def func_assert(self, node, args, kwargs):
          if len(args) == 1:
              FeatureNew('assert function without message argument', '0.53.0').use(self.subproject)
              value = args[0]
              message = None
          elif len(args) == 2:
              value, message = args
              if not isinstance(message, str):
                  raise InterpreterException('Assert message not a string.')
          else:
              raise InterpreterException('Assert takes between one and two arguments')
          if not isinstance(value, bool):
              raise InterpreterException('Assert value not bool.')
          if not value:
              if message is None:
                  from .ast import AstPrinter
                  printer = AstPrinter()
                  node.args.arguments[0].accept(printer)
                  message = printer.result
              raise InterpreterException('Assert failed: ' + message)
  
      def validate_arguments(self, args, argcount, arg_types):
          if argcount is not None:
              if argcount != len(args):
                  raise InvalidArguments('Expected %d arguments, got %d.' %
                                         (argcount, len(args)))
          for actual, wanted in zip(args, arg_types):
              if wanted is not None:
                  if not isinstance(actual, wanted):
                      raise InvalidArguments('Incorrect argument type.')
  
      @FeatureNewKwargs('run_command', '0.50.0', ['env'])
      @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture'])
      @permittedKwargs(permitted_kwargs['run_command'])
      def func_run_command(self, node, args, kwargs):
          return self.run_command_impl(node, args, kwargs)
  
      def run_command_impl(self, node, args, kwargs, in_builddir=False):
          if len(args) < 1:
              raise InterpreterException('Not enough arguments')
          cmd, *cargs = args
          capture = kwargs.get('capture', True)
          srcdir = self.environment.get_source_dir()
          builddir = self.environment.get_build_dir()
  
          check = kwargs.get('check', False)
          if not isinstance(check, bool):
              raise InterpreterException('Check must be boolean.')
  
          env = self.unpack_env_kwarg(kwargs)
  
          m = 'must be a string, or the output of find_program(), files() '\
              'or configure_file(), or a compiler object; not {!r}'
          expanded_args = []
          if isinstance(cmd, ExternalProgramHolder):
              cmd = cmd.held_object
              if isinstance(cmd, build.Executable):
                  progname = node.args.arguments[0].value
                  msg = 'Program {!r} was overridden with the compiled executable {!r}'\
                        ' and therefore cannot be used during configuration'
                  raise InterpreterException(msg.format(progname, cmd.description()))
              if not cmd.found():
                  raise InterpreterException('command {!r} not found or not executable'.format(cmd.get_name()))
          elif isinstance(cmd, CompilerHolder):
              exelist = cmd.compiler.get_exelist()
              cmd = exelist[0]
              prog = ExternalProgram(cmd, silent=True)
              if not prog.found():
                  raise InterpreterException('Program {!r} not found '
                                             'or not executable'.format(cmd))
              cmd = prog
              expanded_args = exelist[1:]
          else:
              if isinstance(cmd, mesonlib.File):
                  cmd = cmd.absolute_path(srcdir, builddir)
              elif not isinstance(cmd, str):
                  raise InterpreterException('First argument ' + m.format(cmd))
              # Prefer scripts in the current source directory
              search_dir = os.path.join(srcdir, self.subdir)
              prog = ExternalProgram(cmd, silent=True, search_dir=search_dir)
              if not prog.found():
                  raise InterpreterException('Program or command {!r} not found '
                                             'or not executable'.format(cmd))
              cmd = prog
          for a in listify(cargs):
              if isinstance(a, str):
                  expanded_args.append(a)
              elif isinstance(a, mesonlib.File):
                  expanded_args.append(a.absolute_path(srcdir, builddir))
              elif isinstance(a, ExternalProgramHolder):
                  expanded_args.append(a.held_object.get_path())
              else:
                  raise InterpreterException('Arguments ' + m.format(a))
          # If any file that was used as an argument to the command
          # changes, we must re-run the configuration step.
          self.add_build_def_file(cmd.get_path())
          for a in expanded_args:
              if not os.path.isabs(a):
                  a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a)
              self.add_build_def_file(a)
          return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir,
                            self.environment.get_build_command() + ['introspect'],
                            in_builddir=in_builddir, check=check, capture=capture)
  
      @stringArgs
      def func_gettext(self, nodes, args, kwargs):
          raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead')
  
      def func_option(self, nodes, args, kwargs):
          raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.')
  
      @FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
      @permittedKwargs(permitted_kwargs['subproject'])
      @stringArgs
      def func_subproject(self, nodes, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Subproject takes exactly one argument')
          dirname = args[0]
          return self.do_subproject(dirname, 'meson', kwargs)
  
      def disabled_subproject(self, dirname, disabled_feature=None, exception=None):
          sub = SubprojectHolder(None, self.subproject_dir, dirname,
                                 disabled_feature=disabled_feature, exception=exception)
          self.subprojects[dirname] = sub
          return sub
  
      def do_subproject(self, dirname: str, method: str, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.disabled_subproject(dirname, disabled_feature=feature)
  
          default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
          default_options = coredata.create_options_dict(default_options)
          if dirname == '':
              raise InterpreterException('Subproject dir name must not be empty.')
          if dirname[0] == '.':
              raise InterpreterException('Subproject dir name must not start with a period.')
          if '..' in dirname:
              raise InterpreterException('Subproject name must not contain a ".." path segment.')
          if os.path.isabs(dirname):
              raise InterpreterException('Subproject name must not be an absolute path.')
          if has_path_sep(dirname):
              mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.',
                           location=self.current_node)
          if dirname in self.subproject_stack:
              fullstack = self.subproject_stack + [dirname]
              incpath = ' => '.join(fullstack)
              raise InvalidCode('Recursive include of subprojects: %s.' % incpath)
          if dirname in self.subprojects:
              subproject = self.subprojects[dirname]
              if required and not subproject.found():
                  raise InterpreterException('Subproject "%s/%s" required but not found.' % (
                                             self.subproject_dir, dirname))
              return subproject
  
          subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
          r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode'))
          try:
              resolved = r.resolve(dirname, method)
          except wrap.WrapException as e:
              subprojdir = os.path.join(self.subproject_dir, r.directory)
              if isinstance(e, wrap.WrapNotFoundException):
                  # if the reason subproject execution failed was because
                  # the directory doesn't exist, try to give some helpful
                  # advice if it's a nested subproject that needs
                  # promotion...
                  self.print_nested_info(dirname)
              if not required:
                  mlog.log(e)
                  mlog.log('Subproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)')
                  return self.disabled_subproject(dirname, exception=e)
              raise e
  
          subdir = os.path.join(self.subproject_dir, resolved)
          subdir_abs = os.path.join(subproject_dir_abs, resolved)
          os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
          self.global_args_frozen = True
  
          mlog.log()
          with mlog.nested():
              mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n')
          try:
              if method == 'meson':
                  return self._do_subproject_meson(dirname, subdir, default_options, kwargs)
              elif method == 'cmake':
                  return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, kwargs)
              else:
                  raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname))
          # Invalid code is always an error
          except InvalidCode:
              raise
          except Exception as e:
              if not required:
                  with mlog.nested():
                      # Suppress the 'ERROR:' prefix because this exception is not
                      # fatal and VS CI treat any logs with "ERROR:" as fatal.
                      mlog.exception(e, prefix=mlog.yellow('Exception:'))
                  mlog.log('\nSubproject', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)')
                  return self.disabled_subproject(dirname, exception=e)
              raise e
  
      def _do_subproject_meson(self, dirname, subdir, default_options, kwargs, ast=None, build_def_files=None):
          with mlog.nested():
              new_build = self.build.copy()
              subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
                                 self.modules, default_options, ast=ast)
              subi.subprojects = self.subprojects
  
              subi.subproject_stack = self.subproject_stack + [dirname]
              current_active = self.active_projectname
              current_warnings_counter = mlog.log_warnings_counter
              mlog.log_warnings_counter = 0
              subi.run()
              subi_warnings = mlog.log_warnings_counter
              mlog.log_warnings_counter = current_warnings_counter
  
              mlog.log('Subproject', mlog.bold(dirname), 'finished.')
  
          mlog.log()
  
          if 'version' in kwargs:
              pv = subi.project_version
              wanted = kwargs['version']
              if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
                  raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted))
          self.active_projectname = current_active
          self.subprojects.update(subi.subprojects)
          self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname,
                                                       warnings=subi_warnings)
          # Duplicates are possible when subproject uses files from project root
          if build_def_files:
              self.build_def_files = list(set(self.build_def_files + build_def_files))
          else:
              self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
          self.build.merge(subi.build)
          self.build.subprojects[dirname] = subi.project_version
          self.summary.update(subi.summary)
          return self.subprojects[dirname]
  
      def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwargs):
          with mlog.nested():
              new_build = self.build.copy()
              prefix = self.coredata.builtins['prefix'].value
              cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
              cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend)
              cm_int.initialise(cmake_options)
              cm_int.analyse()
  
              # Generate a meson ast and execute it with the normal do_subproject_meson
              ast = cm_int.pretend_to_be_meson()
  
              mlog.log()
              with mlog.nested():
                  mlog.log('Processing generated meson AST')
  
                  # Debug print the generated meson file
                  from .ast import AstIndentationGenerator, AstPrinter
                  printer = AstPrinter()
                  ast.accept(AstIndentationGenerator())
                  ast.accept(printer)
                  printer.post_process()
                  meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build')
                  with open(meson_filename, "w") as f:
                      f.write(printer.result)
  
                  mlog.log('Build file:', meson_filename)
                  mlog.cmd_ci_include(meson_filename)
                  mlog.log()
  
              result = self._do_subproject_meson(dirname, subdir, default_options, kwargs, ast, cm_int.bs_files)
              result.cm_interpreter = cm_int
  
          mlog.log()
          return result
  
      def get_option_internal(self, optname):
          raw_optname = optname
          if self.is_subproject():
              optname = self.subproject + ':' + optname
  
          for opts in [
                  self.coredata.base_options, compilers.base_options, self.coredata.builtins,
                  dict(self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine)),
                  dict(self.coredata.flatten_lang_iterator(
                      self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options))),
          ]:
              v = opts.get(optname)
              if v is None or v.yielding:
                  v = opts.get(raw_optname)
              if v is not None:
                  return v
  
          try:
              opt = self.coredata.user_options[optname]
              if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options:
                  popt = self.coredata.user_options[raw_optname]
                  if type(opt) is type(popt):
                      opt = popt
                  else:
                      # Get class name, then option type as a string
                      opt_type = opt.__class__.__name__[4:][:-6].lower()
                      popt_type = popt.__class__.__name__[4:][:-6].lower()
                      # This is not a hard error to avoid dependency hell, the workaround
                      # when this happens is to simply set the subproject's option directly.
                      mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield '
                                   'to parent option of type {3!r}, ignoring parent value. '
                                   'Use -D{2}:{0}=value to set the value for this option manually'
                                   '.'.format(raw_optname, opt_type, self.subproject, popt_type),
                                   location=self.current_node)
              return opt
          except KeyError:
              pass
  
          raise InterpreterException('Tried to access unknown option "%s".' % optname)
  
      @stringArgs
      @noKwargs
      def func_get_option(self, nodes, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Argument required for get_option.')
          optname = args[0]
          if ':' in optname:
              raise InterpreterException('Having a colon in option name is forbidden, '
                                         'projects are not allowed to directly access '
                                         'options of other subprojects.')
          opt = self.get_option_internal(optname)
          if isinstance(opt, coredata.UserFeatureOption):
              return FeatureOptionHolder(self.environment, optname, opt)
          elif isinstance(opt, coredata.UserOption):
              return opt.value
          return opt
  
      @noKwargs
      def func_configuration_data(self, node, args, kwargs):
          if len(args) > 1:
              raise InterpreterException('configuration_data takes only one optional positional arguments')
          elif len(args) == 1:
              FeatureNew('configuration_data dictionary', '0.49.0').use(self.subproject)
              initial_values = args[0]
              if not isinstance(initial_values, dict):
                  raise InterpreterException('configuration_data first argument must be a dictionary')
          else:
              initial_values = {}
          return ConfigurationDataHolder(self.subproject, initial_values)
  
      def set_backend(self):
          # The backend is already set when parsing subprojects
          if self.backend is not None:
              return
          backend = self.coredata.get_builtin_option('backend')
          from .backend import backends
          self.backend = backends.get_backend_from_name(backend, self.build, self)
  
          if self.backend is None:
              raise InterpreterException('Unknown backend "%s".' % backend)
          if backend != self.backend.name:
              if self.backend.name.startswith('vs'):
                  mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
              self.coredata.set_builtin_option('backend', self.backend.name)
  
          # Only init backend options on first invocation otherwise it would
          # override values previously set from command line.
          if self.environment.first_invocation:
              self.coredata.init_backend_options(backend)
  
          options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')}
          self.coredata.set_options(options)
  
      @stringArgs
      @permittedKwargs(permitted_kwargs['project'])
      def func_project(self, node, args, kwargs):
          if len(args) < 1:
              raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
          proj_name, *proj_langs = args
          if ':' in proj_name:
              raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name))
  
          if 'meson_version' in kwargs:
              cv = coredata.version
              pv = kwargs['meson_version']
              if not mesonlib.version_compare(cv, pv):
                  raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv))
  
          if os.path.exists(self.option_file):
              oi = optinterpreter.OptionInterpreter(self.subproject)
              oi.process(self.option_file)
              self.coredata.merge_user_options(oi.options)
              self.add_build_def_file(self.option_file)
  
          # Do not set default_options on reconfigure otherwise it would override
          # values previously set from command line. That means that changing
          # default_options in a project will trigger a reconfigure but won't
          # have any effect.
          self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', []))
          self.project_default_options = coredata.create_options_dict(self.project_default_options)
          if self.environment.first_invocation:
              default_options = self.project_default_options
              default_options.update(self.default_project_options)
              self.coredata.init_builtins(self.subproject)
          else:
              default_options = {}
          self.coredata.set_default_options(default_options, self.subproject, self.environment)
  
          if not self.is_subproject():
              self.build.project_name = proj_name
          self.active_projectname = proj_name
          self.project_version = kwargs.get('version', 'undefined')
          if self.build.project_version is None:
              self.build.project_version = self.project_version
          proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown'))
          self.build.dep_manifest[proj_name] = {'version': self.project_version,
                                                'license': proj_license}
          if self.subproject in self.build.projects:
              raise InvalidCode('Second call to project().')
          if not self.is_subproject() and 'subproject_dir' in kwargs:
              spdirname = kwargs['subproject_dir']
              if not isinstance(spdirname, str):
                  raise InterpreterException('Subproject_dir must be a string')
              if os.path.isabs(spdirname):
                  raise InterpreterException('Subproject_dir must not be an absolute path.')
              if spdirname.startswith('.'):
                  raise InterpreterException('Subproject_dir must not begin with a period.')
              if '..' in spdirname:
                  raise InterpreterException('Subproject_dir must not contain a ".." segment.')
              self.subproject_dir = spdirname
  
          self.build.subproject_dir = self.subproject_dir
  
          mesonlib.project_meson_versions[self.subproject] = ''
          if 'meson_version' in kwargs:
              mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
  
          self.build.projects[self.subproject] = proj_name
          mlog.log('Project name:', mlog.bold(proj_name))
          mlog.log('Project version:', mlog.bold(self.project_version))
          self.add_languages(proj_langs, True, MachineChoice.BUILD)
          self.add_languages(proj_langs, True, MachineChoice.HOST)
          self.set_backend()
          if not self.is_subproject():
              self.check_stdlibs()
  
      @FeatureNewKwargs('add_languages', '0.54.0', ['native'])
      @permittedKwargs(permitted_kwargs['add_languages'])
      @stringArgs
      def func_add_languages(self, node, args, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              for lang in sorted(args, key=compilers.sort_clink):
                  mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled')
              return False
          if 'native' in kwargs:
              return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs))
          else:
              # absent 'native' means 'both' for backwards compatibility
              mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.',
                           location=self.current_node)
              success = self.add_languages(args, False, MachineChoice.BUILD)
              success &= self.add_languages(args, required, MachineChoice.HOST)
              return success
  
      def get_message_string_arg(self, arg):
          if isinstance(arg, list):
              argstr = stringifyUserArguments(arg)
          elif isinstance(arg, dict):
              argstr = stringifyUserArguments(arg)
          elif isinstance(arg, str):
              argstr = arg
          elif isinstance(arg, int):
              argstr = str(arg)
          else:
              raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
  
          return argstr
  
      @noArgsFlattening
      @noKwargs
      def func_message(self, node, args, kwargs):
          if len(args) > 1:
              FeatureNew('message with more than one argument', '0.54.0').use(self.subproject)
          args_str = [self.get_message_string_arg(i) for i in args]
          self.message_impl(args_str)
  
      def message_impl(self, args):
          mlog.log(mlog.bold('Message:'), *args)
  
      @noArgsFlattening
      @FeatureNewKwargs('summary', '0.54.0', ['list_sep'])
      @permittedKwargs({'section', 'bool_yn', 'list_sep'})
      @FeatureNew('summary', '0.53.0')
      def func_summary(self, node, args, kwargs):
          if len(args) == 1:
              if not isinstance(args[0], dict):
                  raise InterpreterException('Summary first argument must be dictionary.')
              values = args[0]
          elif len(args) == 2:
              if not isinstance(args[0], str):
                  raise InterpreterException('Summary first argument must be string.')
              values = {args[0]: args[1]}
          else:
              raise InterpreterException('Summary accepts at most 2 arguments.')
          section = kwargs.get('section', '')
          if not isinstance(section, str):
              raise InterpreterException('Summary\'s section keyword argument must be string.')
          self.summary_impl(section, values, kwargs)
  
      def summary_impl(self, section, values, kwargs):
          if self.subproject not in self.summary:
              self.summary[self.subproject] = Summary(self.active_projectname, self.project_version)
          self.summary[self.subproject].add_section(section, values, kwargs)
  
      def _print_summary(self):
          # Add automatic 'Supbrojects' section in main project.
          all_subprojects = collections.OrderedDict()
          for name, subp in sorted(self.subprojects.items()):
              value = subp.found()
              if subp.disabled_feature:
                  value = [value, 'Feature {!r} disabled'.format(subp.disabled_feature)]
              elif subp.exception:
                  value = [value, str(subp.exception)]
              elif subp.warnings > 0:
                  value = [value, '{} warnings'.format(subp.warnings)]
              all_subprojects[name] = value
          if all_subprojects:
              self.summary_impl('Subprojects', all_subprojects,
                                {'bool_yn': True,
                                 'list_sep': ' ',
                                })
          # Print all summaries, main project last.
          mlog.log('')  # newline
          main_summary = self.summary.pop('', None)
          for _, summary in sorted(self.summary.items()):
              summary.dump()
          if main_summary:
              main_summary.dump()
  
      @noArgsFlattening
      @FeatureNew('warning', '0.44.0')
      @noKwargs
      def func_warning(self, node, args, kwargs):
          if len(args) > 1:
              FeatureNew('warning with more than one argument', '0.54.0').use(self.subproject)
          args_str = [self.get_message_string_arg(i) for i in args]
          mlog.warning(*args_str, location=node)
  
      @noKwargs
      def func_error(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          raise InterpreterException('Problem encountered: ' + args[0])
  
      @noKwargs
      def func_exception(self, node, args, kwargs):
          self.validate_arguments(args, 0, [])
          raise Exception()
  
      def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool:
          success = self.add_languages_for(args, required, for_machine)
          if not self.coredata.is_cross_build():
              self.coredata.copy_build_options_from_regular_ones()
          self._redetect_machines()
          return success
  
      def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool:
          if for_machine != MachineChoice.HOST:
              return False
          if not self.environment.is_cross_build():
              return False
          should = self.environment.properties.host.get('skip_sanity_check', False)
          if not isinstance(should, bool):
              raise InterpreterException('Option skip_sanity_check must be a boolean.')
          return should
  
      def add_languages_for(self, args, required, for_machine: MachineChoice):
          success = True
          for lang in sorted(args, key=compilers.sort_clink):
              lang = lang.lower()
              clist = self.coredata.compilers[for_machine]
              machine_name = for_machine.get_lower_case_name()
              if lang in clist:
                  comp = clist[lang]
              else:
                  try:
                      comp = self.environment.detect_compiler_for(lang, for_machine)
                      if comp is None:
                          raise InvalidArguments('Tried to use unknown language "%s".' % lang)
                      if self.should_skip_sanity_check(for_machine):
                          mlog.log_once('Cross compiler sanity tests disabled via the cross file.')
                      else:
                          comp.sanity_check(self.environment.get_scratch_dir(), self.environment)
                  except Exception:
                      if not required:
                          mlog.log('Compiler for language',
                                   mlog.bold(lang), 'for the', machine_name,
                                   'machine not found.')
                          success = False
                          continue
                      else:
                          raise
  
              if for_machine == MachineChoice.HOST or self.environment.is_cross_build():
                  logger_fun = mlog.log
              else:
                  logger_fun = mlog.debug
              logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:',
                         mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string())
              if comp.linker is not None:
                  logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:',
                             mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version)
              self.build.ensure_static_linker(comp)
  
          langs = self.coredata.compilers[for_machine].keys()
          if 'vala' in langs:
              if 'c' not in langs:
                  raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.')
  
          return success
  
      def program_from_file_for(self, for_machine, prognames, silent):
          for p in unholder(prognames):
              if isinstance(p, mesonlib.File):
                  continue # Always points to a local (i.e. self generated) file.
              if not isinstance(p, str):
                  raise InterpreterException('Executable name must be a string')
              prog = ExternalProgram.from_bin_list(self.environment, for_machine, p)
              if prog.found():
                  return ExternalProgramHolder(prog, self.subproject)
          return None
  
      def program_from_system(self, args, search_dirs, silent=False):
          # Search for scripts relative to current subdir.
          # Do not cache found programs because find_program('foobar')
          # might give different results when run from different source dirs.
          source_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
          for exename in args:
              if isinstance(exename, mesonlib.File):
                  if exename.is_built:
                      search_dir = os.path.join(self.environment.get_build_dir(),
                                                exename.subdir)
                  else:
                      search_dir = os.path.join(self.environment.get_source_dir(),
                                                exename.subdir)
                  exename = exename.fname
                  extra_search_dirs = []
              elif isinstance(exename, str):
                  search_dir = source_dir
                  extra_search_dirs = search_dirs
              else:
                  raise InvalidArguments('find_program only accepts strings and '
                                         'files, not {!r}'.format(exename))
              extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
                                                     extra_search_dirs=extra_search_dirs,
                                                     silent=silent)
              progobj = ExternalProgramHolder(extprog, self.subproject)
              if progobj.found():
                  return progobj
  
      def program_from_overrides(self, command_names, silent=False):
          for name in command_names:
              if not isinstance(name, str):
                  continue
              if name in self.build.find_overrides:
                  exe = self.build.find_overrides[name]
                  if not silent:
                      mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
                               '(overridden: %s)' % exe.description())
                  return ExternalProgramHolder(exe, self.subproject, self.backend)
          return None
  
      def store_name_lookups(self, command_names):
          for name in command_names:
              if isinstance(name, str):
                  self.build.searched_programs.add(name)
  
      def add_find_program_override(self, name, exe):
          if name in self.build.searched_programs:
              raise InterpreterException('Tried to override finding of executable "%s" which has already been found.'
                                         % name)
          if name in self.build.find_overrides:
              raise InterpreterException('Tried to override executable "%s" which has already been overridden.'
                                         % name)
          self.build.find_overrides[name] = exe
  
      # TODO update modules to always pass `for_machine`. It is bad-form to assume
      # the host machine.
      def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST,
                            required=True, silent=True, wanted='', search_dirs=None):
          if not isinstance(args, list):
              args = [args]
  
          progobj = self.program_from_overrides(args, silent=silent)
          if progobj is None:
              progobj = self.program_from_file_for(for_machine, args, silent=silent)
          if progobj is None:
              progobj = self.program_from_system(args, search_dirs, silent=silent)
          if progobj is None and args[0].endswith('python3'):
              prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True)
              progobj = ExternalProgramHolder(prog, self.subproject)
          if required and (progobj is None or not progobj.found()):
              raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args))
          if progobj is None:
              return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
          # Only store successful lookups
          self.store_name_lookups(args)
          if wanted:
              version = progobj.get_version(self)
              is_found, not_found, found = mesonlib.version_compare_many(version, wanted)
              if not is_found:
                  mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'),
                           'found {!r} but need:'.format(version),
                           ', '.join(["'{}'".format(e) for e in not_found]))
                  if required:
                      m = 'Invalid version of program, need {!r} {!r} found {!r}.'
                      raise InvalidArguments(m.format(progobj.get_name(), not_found, version))
                  return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
          return progobj
  
      @FeatureNewKwargs('find_program', '0.53.0', ['dirs'])
      @FeatureNewKwargs('find_program', '0.52.0', ['version'])
      @FeatureNewKwargs('find_program', '0.49.0', ['disabler'])
      @disablerIfNotFound
      @permittedKwargs(permitted_kwargs['find_program'])
      def func_find_program(self, node, args, kwargs):
          if not args:
              raise InterpreterException('No program name specified.')
  
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled')
              return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
  
          search_dirs = extract_search_dirs(kwargs)
          wanted = mesonlib.stringlistify(kwargs.get('version', []))
          for_machine = self.machine_from_native_kwarg(kwargs)
          return self.find_program_impl(args, for_machine, required=required,
                                        silent=False, wanted=wanted,
                                        search_dirs=search_dirs)
  
      def func_find_library(self, node, args, kwargs):
          raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
                            'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n'
                            'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
                            )
  
      def _find_cached_dep(self, name, display_name, kwargs):
          # Check if we want this as a build-time / build machine or runt-time /
          # host machine dep.
          for_machine = self.machine_from_native_kwarg(kwargs)
          identifier = dependencies.get_dep_identifier(name, kwargs)
          wanted_vers = mesonlib.stringlistify(kwargs.get('version', []))
  
          override = self.build.dependency_overrides[for_machine].get(identifier)
          if override:
              info = [mlog.blue('(overridden)' if override.explicit else '(cached)')]
              cached_dep = override.dep
              # We don't implicitly override not-found dependencies, but user could
              # have explicitly called meson.override_dependency() with a not-found
              # dep.
              if not cached_dep.found():
                  mlog.log('Dependency', mlog.bold(display_name),
                           'found:', mlog.red('NO'), *info)
                  return identifier, cached_dep
              found_vers = cached_dep.get_version()
              if not self.check_version(wanted_vers, found_vers):
                  mlog.log('Dependency', mlog.bold(name),
                           'found:', mlog.red('NO'),
                           'found', mlog.normal_cyan(found_vers), 'but need:',
                           mlog.bold(', '.join(["'{}'".format(e) for e in wanted_vers])),
                           *info)
                  return identifier, NotFoundDependency(self.environment)
          else:
              info = [mlog.blue('(cached)')]
              cached_dep = self.coredata.deps[for_machine].get(identifier)
              if cached_dep:
                  found_vers = cached_dep.get_version()
                  if not self.check_version(wanted_vers, found_vers):
                      return identifier, None
  
          if cached_dep:
              if found_vers:
                  info = [mlog.normal_cyan(found_vers), *info]
              mlog.log('Dependency', mlog.bold(display_name),
                       'found:', mlog.green('YES'), *info)
              return identifier, cached_dep
  
          return identifier, None
  
      @staticmethod
      def check_version(wanted, found):
          if not wanted:
              return True
          if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]:
              return False
          return True
  
      def notfound_dependency(self):
          return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
  
      def verify_fallback_consistency(self, dirname, varname, cached_dep):
          subi = self.subprojects.get(dirname)
          if not cached_dep or not varname or not subi or not cached_dep.found():
              return
          dep = subi.get_variable_method([varname], {})
          if dep.held_object != cached_dep:
              m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}'
              raise DependencyException(m.format(varname))
  
      def get_subproject_dep(self, name, display_name, dirname, varname, kwargs):
          required = kwargs.get('required', True)
          wanted = mesonlib.stringlistify(kwargs.get('version', []))
          subproj_path = os.path.join(self.subproject_dir, dirname)
          dep = self.notfound_dependency()
          try:
              subproject = self.subprojects[dirname]
              _, cached_dep = self._find_cached_dep(name, display_name, kwargs)
              if varname is None:
                  # Assuming the subproject overridden the dependency we want
                  if cached_dep:
                      if required and not cached_dep.found():
                          m = 'Dependency {!r} is not satisfied'
                          raise DependencyException(m.format(display_name))
                      return DependencyHolder(cached_dep, self.subproject)
                  else:
                      m = 'Subproject {} did not override dependency {}'
                      raise DependencyException(m.format(subproj_path, display_name))
              if subproject.found():
                  self.verify_fallback_consistency(dirname, varname, cached_dep)
                  dep = self.subprojects[dirname].get_variable_method([varname], {})
          except InvalidArguments:
              pass
  
          if not isinstance(dep, DependencyHolder):
              raise InvalidCode('Fetched variable {!r} in the subproject {!r} is '
                                'not a dependency object.'.format(varname, dirname))
  
          if not dep.found():
              if required:
                  raise DependencyException('Could not find dependency {} in subproject {}'
                                            ''.format(varname, dirname))
              # If the dependency is not required, don't raise an exception
              mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                       mlog.bold(subproj_path), 'found:', mlog.red('NO'))
              return dep
  
          found = dep.held_object.get_version()
          if not self.check_version(wanted, found):
              if required:
                  raise DependencyException('Version {} of subproject dependency {} already '
                                            'cached, requested incompatible version {} for '
                                            'dep {}'.format(found, dirname, wanted, display_name))
  
              mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                       mlog.bold(subproj_path), 'found:', mlog.red('NO'),
                       'found', mlog.normal_cyan(found), 'but need:',
                       mlog.bold(', '.join(["'{}'".format(e) for e in wanted])))
              return self.notfound_dependency()
  
          found = mlog.normal_cyan(found) if found else None
          mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
                   mlog.bold(subproj_path), 'found:', mlog.green('YES'), found)
          return dep
  
      def _handle_featurenew_dependencies(self, name):
          'Do a feature check on dependencies used by this subproject'
          if name == 'mpi':
              FeatureNew('MPI Dependency', '0.42.0').use(self.subproject)
          elif name == 'pcap':
              FeatureNew('Pcap Dependency', '0.42.0').use(self.subproject)
          elif name == 'vulkan':
              FeatureNew('Vulkan Dependency', '0.42.0').use(self.subproject)
          elif name == 'libwmf':
              FeatureNew('LibWMF Dependency', '0.44.0').use(self.subproject)
          elif name == 'openmp':
              FeatureNew('OpenMP Dependency', '0.46.0').use(self.subproject)
  
      @FeatureNewKwargs('dependency', '0.54.0', ['components'])
      @FeatureNewKwargs('dependency', '0.52.0', ['include_type'])
      @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args'])
      @FeatureNewKwargs('dependency', '0.49.0', ['disabler'])
      @FeatureNewKwargs('dependency', '0.40.0', ['method'])
      @FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
      @disablerIfNotFound
      @permittedKwargs(permitted_kwargs['dependency'])
      def func_dependency(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          name = args[0]
          display_name = name if name else '(anonymous)'
          mods = extract_as_list(kwargs, 'modules')
          if mods:
              display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
          not_found_message = kwargs.get('not_found_message', '')
          if not isinstance(not_found_message, str):
              raise InvalidArguments('The not_found_message must be a string.')
          try:
              d = self.dependency_impl(name, display_name, kwargs)
          except Exception:
              if not_found_message:
                  self.message_impl([not_found_message])
              raise
          if not d.found() and not_found_message:
              self.message_impl([not_found_message])
              self.message_impl([not_found_message])
          # Override this dependency to have consistent results in subsequent
          # dependency lookups.
          if name and d.found():
              for_machine = self.machine_from_native_kwarg(kwargs)
              identifier = dependencies.get_dep_identifier(name, kwargs)
              if identifier not in self.build.dependency_overrides[for_machine]:
                  self.build.dependency_overrides[for_machine][identifier] = \
                      build.DependencyOverride(d.held_object, node, explicit=False)
          return d
  
      def dependency_impl(self, name, display_name, kwargs):
          disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
          if disabled:
              mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
              return self.notfound_dependency()
  
          has_fallback = 'fallback' in kwargs
          if 'default_options' in kwargs and not has_fallback:
              mlog.warning('The "default_options" keyworg argument does nothing without a "fallback" keyword argument.',
                           location=self.current_node)
  
          # writing just "dependency('')" is an error, because it can only fail
          if name == '' and required and not has_fallback:
              raise InvalidArguments('Dependency is both required and not-found')
  
          if '<' in name or '>' in name or '=' in name:
              raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
                                     'version\n requirements use the \'version\' keyword argument instead.')
  
          identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs)
          if cached_dep:
              if has_fallback:
                  dirname, varname = self.get_subproject_infos(kwargs)
                  self.verify_fallback_consistency(dirname, varname, cached_dep)
              if required and not cached_dep.found():
                  m = 'Dependency {!r} was already checked and was not found'
                  raise DependencyException(m.format(display_name))
              return DependencyHolder(cached_dep, self.subproject)
  
          # If the dependency has already been configured, possibly by
          # a higher level project, try to use it first.
          if has_fallback:
              dirname, varname = self.get_subproject_infos(kwargs)
              if dirname in self.subprojects:
                  return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
  
          wrap_mode = self.coredata.get_builtin_option('wrap_mode')
          forcefallback = wrap_mode == WrapMode.forcefallback and has_fallback
          if name != '' and not forcefallback:
              self._handle_featurenew_dependencies(name)
              kwargs['required'] = required and not has_fallback
              dep = dependencies.find_external_dependency(name, self.environment, kwargs)
              kwargs['required'] = required
              # Only store found-deps in the cache
              # Never add fallback deps to self.coredata.deps since we
              # cannot cache them. They must always be evaluated else
              # we won't actually read all the build files.
              if dep.found():
                  for_machine = self.machine_from_native_kwarg(kwargs)
                  self.coredata.deps[for_machine].put(identifier, dep)
                  return DependencyHolder(dep, self.subproject)
  
          if has_fallback:
              return self.dependency_fallback(name, display_name, kwargs)
  
          return self.notfound_dependency()
  
      @FeatureNew('disabler', '0.44.0')
      @noKwargs
      @noPosargs
      def func_disabler(self, node, args, kwargs):
          return Disabler()
  
      def print_nested_info(self, dependency_name):
          message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' +
                     'To use it in the current project, promote it by going in the project source\n'
                     'root and issuing']
          sprojs = mesonlib.detect_subprojects('subprojects', self.source_root)
          if dependency_name not in sprojs:
              return
          found = sprojs[dependency_name]
          if len(found) > 1:
              message.append('one of the following commands:')
          else:
              message.append('the following command:')
          command_templ = '\nmeson wrap promote {}'
          for l in found:
              message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:])))
          mlog.warning(*message, location=self.current_node)
  
      def get_subproject_infos(self, kwargs):
          fbinfo = mesonlib.stringlistify(kwargs['fallback'])
          if len(fbinfo) == 1:
              FeatureNew('Fallback without variable name', '0.53.0').use(self.subproject)
              return fbinfo[0], None
          elif len(fbinfo) != 2:
              raise InterpreterException('Fallback info must have one or two items.')
          return fbinfo
  
      def dependency_fallback(self, name, display_name, kwargs):
          required = kwargs.get('required', True)
          if self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback:
              mlog.log('Not looking for a fallback subproject for the dependency',
                       mlog.bold(display_name), 'because:\nUse of fallback '
                       'dependencies is disabled.')
              if required:
                  m = 'Dependency {!r} not found and fallback is disabled'
                  raise DependencyException(m.format(display_name))
              return self.notfound_dependency()
          elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.forcefallback:
              mlog.log('Looking for a fallback subproject for the dependency',
                       mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.')
          else:
              mlog.log('Looking for a fallback subproject for the dependency',
                       mlog.bold(display_name))
          dirname, varname = self.get_subproject_infos(kwargs)
          sp_kwargs = {
              'default_options': kwargs.get('default_options', []),
              'required': required,
          }
          self.do_subproject(dirname, 'meson', sp_kwargs)
          return self.get_subproject_dep(name, display_name, dirname, varname, kwargs)
  
      @FeatureNewKwargs('executable', '0.42.0', ['implib'])
      @permittedKwargs(permitted_kwargs['executable'])
      def func_executable(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, ExecutableHolder)
  
      @permittedKwargs(permitted_kwargs['static_library'])
      def func_static_lib(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, StaticLibraryHolder)
  
      @permittedKwargs(permitted_kwargs['shared_library'])
      def func_shared_lib(self, node, args, kwargs):
          holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
          holder.held_object.shared_library_only = True
          return holder
  
      @permittedKwargs(permitted_kwargs['both_libraries'])
      def func_both_lib(self, node, args, kwargs):
          return self.build_both_libraries(node, args, kwargs)
  
      @FeatureNew('shared_module', '0.37.0')
      @permittedKwargs(permitted_kwargs['shared_module'])
      def func_shared_module(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, SharedModuleHolder)
  
      @permittedKwargs(permitted_kwargs['library'])
      def func_library(self, node, args, kwargs):
          return self.build_library(node, args, kwargs)
  
      @permittedKwargs(permitted_kwargs['jar'])
      def func_jar(self, node, args, kwargs):
          return self.build_target(node, args, kwargs, JarHolder)
  
      @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
      @permittedKwargs(permitted_kwargs['build_target'])
      def func_build_target(self, node, args, kwargs):
          if 'target_type' not in kwargs:
              raise InterpreterException('Missing target_type keyword argument')
          target_type = kwargs.pop('target_type')
          if target_type == 'executable':
              return self.build_target(node, args, kwargs, ExecutableHolder)
          elif target_type == 'shared_library':
              return self.build_target(node, args, kwargs, SharedLibraryHolder)
          elif target_type == 'shared_module':
              FeatureNew('build_target(target_type: \'shared_module\')',
                         '0.51.0').use(self.subproject)
              return self.build_target(node, args, kwargs, SharedModuleHolder)
          elif target_type == 'static_library':
              return self.build_target(node, args, kwargs, StaticLibraryHolder)
          elif target_type == 'both_libraries':
              return self.build_both_libraries(node, args, kwargs)
          elif target_type == 'library':
              return self.build_library(node, args, kwargs)
          elif target_type == 'jar':
              return self.build_target(node, args, kwargs, JarHolder)
          else:
              raise InterpreterException('Unknown target_type.')
  
      @permittedKwargs(permitted_kwargs['vcs_tag'])
      def func_vcs_tag(self, node, args, kwargs):
          if 'input' not in kwargs or 'output' not in kwargs:
              raise InterpreterException('Keyword arguments input and output must exist')
          if 'fallback' not in kwargs:
              FeatureNew('Optional fallback in vcs_tag', '0.41.0').use(self.subproject)
          fallback = kwargs.pop('fallback', self.project_version)
          if not isinstance(fallback, str):
              raise InterpreterException('Keyword argument fallback must be a string.')
          replace_string = kwargs.pop('replace_string', '@VCS_TAG@')
          regex_selector = '(.*)' # default regex selector for custom command: use complete output
          vcs_cmd = kwargs.get('command', None)
          if vcs_cmd and not isinstance(vcs_cmd, list):
              vcs_cmd = [vcs_cmd]
          source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir))
          if vcs_cmd:
              # Is the command an executable in path or maybe a script in the source tree?
              vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0])
          else:
              vcs = mesonlib.detect_vcs(source_dir)
              if vcs:
                  mlog.log('Found %s repository at %s' % (vcs['name'], vcs['wc_dir']))
                  vcs_cmd = vcs['get_rev'].split()
                  regex_selector = vcs['rev_regex']
              else:
                  vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string
          # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command...
          kwargs['command'] = self.environment.get_build_command() + \
              ['--internal',
               'vcstagger',
               '@INPUT0@',
               '@OUTPUT0@',
               fallback,
               source_dir,
               replace_string,
               regex_selector] + vcs_cmd
          kwargs.setdefault('build_by_default', True)
          kwargs.setdefault('build_always_stale', True)
          return self._func_custom_target_impl(node, [kwargs['output']], kwargs)
  
      @FeatureNew('subdir_done', '0.46.0')
      @stringArgs
      def func_subdir_done(self, node, args, kwargs):
          if len(kwargs) > 0:
              raise InterpreterException('exit does not take named arguments')
          if len(args) > 0:
              raise InterpreterException('exit does not take any arguments')
          raise SubdirDoneRequest()
  
      @stringArgs
      @FeatureNewKwargs('custom_target', '0.48.0', ['console'])
      @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale'])
      @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default'])
      @permittedKwargs(permitted_kwargs['custom_target'])
      def func_custom_target(self, node, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
          if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
              FeatureNew('substitutions in custom_target depfile', '0.47.0').use(self.subproject)
          return self._func_custom_target_impl(node, args, kwargs)
  
      def _func_custom_target_impl(self, node, args, kwargs):
          'Implementation-only, without FeatureNew checks, for internal use'
          name = args[0]
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          if 'input' in kwargs:
              try:
                  kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
              except mesonlib.MesonException:
                  mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s).
  This will become a hard error in the future.''' % kwargs['input'], location=self.current_node)
          tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self)
          self.add_target(name, tg.held_object)
          return tg
  
      @permittedKwargs(permitted_kwargs['run_target'])
      def func_run_target(self, node, args, kwargs):
          if len(args) > 1:
              raise InvalidCode('Run_target takes only one positional argument: the target name.')
          elif len(args) == 1:
              if 'command' not in kwargs:
                  raise InterpreterException('Missing "command" keyword argument')
              all_args = extract_as_list(kwargs, 'command')
              deps = unholder(extract_as_list(kwargs, 'depends'))
          else:
              raise InterpreterException('Run_target needs at least one positional argument.')
  
          cleaned_args = []
          for i in unholder(listify(all_args)):
              if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)):
                  mlog.debug('Wrong type:', str(i))
                  raise InterpreterException('Invalid argument to run_target.')
              if isinstance(i, dependencies.ExternalProgram) and not i.found():
                  raise InterpreterException('Tried to use non-existing executable {!r}'.format(i.name))
              cleaned_args.append(i)
          name = args[0]
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string.')
          cleaned_deps = []
          for d in deps:
              if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
                  raise InterpreterException('Depends items must be build targets.')
              cleaned_deps.append(d)
          command, *cmd_args = cleaned_args
          tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self)
          self.add_target(name, tg.held_object)
          full_name = (self.subproject, name)
          assert(full_name not in self.build.run_target_names)
          self.build.run_target_names.add(full_name)
          return tg
  
      @FeatureNew('alias_target', '0.52.0')
      @noKwargs
      def func_alias_target(self, node, args, kwargs):
          if len(args) < 2:
              raise InvalidCode('alias_target takes at least 2 arguments.')
          name = args[0]
          if not isinstance(name, str):
              raise InterpreterException('First argument must be a string.')
          deps = unholder(listify(args[1:]))
          for d in deps:
              if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
                  raise InterpreterException('Depends items must be build targets.')
          tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self)
          self.add_target(name, tg.held_object)
          return tg
  
      @permittedKwargs(permitted_kwargs['generator'])
      def func_generator(self, node, args, kwargs):
          gen = GeneratorHolder(self, args, kwargs)
          self.generators.append(gen)
          return gen
  
      @FeatureNewKwargs('benchmark', '0.46.0', ['depends'])
      @FeatureNewKwargs('benchmark', '0.52.0', ['priority'])
      @permittedKwargs(permitted_kwargs['benchmark'])
      def func_benchmark(self, node, args, kwargs):
          # is_parallel isn't valid here, so make sure it isn't passed
          if 'is_parallel' in kwargs:
              del kwargs['is_parallel']
          self.add_test(node, args, kwargs, False)
  
      @FeatureNewKwargs('test', '0.46.0', ['depends'])
      @FeatureNewKwargs('test', '0.52.0', ['priority'])
      @permittedKwargs(permitted_kwargs['test'])
      def func_test(self, node, args, kwargs):
          self.add_test(node, args, kwargs, True)
  
      def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables:
          envlist = kwargs.get('env', EnvironmentVariablesHolder())
          if isinstance(envlist, EnvironmentVariablesHolder):
              env = envlist.held_object
          elif isinstance(envlist, dict):
              FeatureNew('environment dictionary', '0.52.0').use(self.subproject)
              env = EnvironmentVariablesHolder(envlist)
              env = env.held_object
          else:
              envlist = listify(envlist)
              # Convert from array to environment object
              env = EnvironmentVariablesHolder(envlist)
              env = env.held_object
          return env
  
      def add_test(self, node, args, kwargs, is_base_test):
          if len(args) != 2:
              raise InterpreterException('test expects 2 arguments, {} given'.format(len(args)))
          if not isinstance(args[0], str):
              raise InterpreterException('First argument of test must be a string.')
          exe = args[1]
          if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)):
              if isinstance(exe, mesonlib.File):
                  exe = self.func_find_program(node, args[1], {})
              else:
                  raise InterpreterException('Second argument must be executable.')
          par = kwargs.get('is_parallel', True)
          if not isinstance(par, bool):
              raise InterpreterException('Keyword argument is_parallel must be a boolean.')
          cmd_args = unholder(extract_as_list(kwargs, 'args'))
          for i in cmd_args:
              if not isinstance(i, (str, mesonlib.File, build.Target)):
                  raise InterpreterException('Command line arguments must be strings, files or targets.')
          env = self.unpack_env_kwarg(kwargs)
          should_fail = kwargs.get('should_fail', False)
          if not isinstance(should_fail, bool):
              raise InterpreterException('Keyword argument should_fail must be a boolean.')
          timeout = kwargs.get('timeout', 30)
          if 'workdir' in kwargs:
              workdir = kwargs['workdir']
              if not isinstance(workdir, str):
                  raise InterpreterException('Workdir keyword argument must be a string.')
              if not os.path.isabs(workdir):
                  raise InterpreterException('Workdir keyword argument must be an absolute path.')
          else:
              workdir = None
          if not isinstance(timeout, int):
              raise InterpreterException('Timeout must be an integer.')
          protocol = kwargs.get('protocol', 'exitcode')
          if protocol not in ('exitcode', 'tap'):
              raise InterpreterException('Protocol must be "exitcode" or "tap".')
          suite = []
          prj = self.subproject if self.is_subproject() else self.build.project_name
          for s in mesonlib.stringlistify(kwargs.get('suite', '')):
              if len(s) > 0:
                  s = ':' + s
              suite.append(prj.replace(' ', '_').replace(':', '_') + s)
          depends = unholder(extract_as_list(kwargs, 'depends'))
          for dep in depends:
              if not isinstance(dep, (build.CustomTarget, build.BuildTarget)):
                  raise InterpreterException('Depends items must be build targets.')
          priority = kwargs.get('priority', 0)
          if not isinstance(priority, int):
              raise InterpreterException('Keyword argument priority must be an integer.')
          t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args,
                   env, should_fail, timeout, workdir, protocol, priority)
          if is_base_test:
              self.build.tests.append(t)
              mlog.debug('Adding test', mlog.bold(args[0], True))
          else:
              self.build.benchmarks.append(t)
              mlog.debug('Adding benchmark', mlog.bold(args[0], True))
  
      @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_headers'])
      def func_install_headers(self, node, args, kwargs):
          source_files = self.source_strings_to_files(args)
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          h = Headers(source_files, kwargs)
          self.build.headers.append(h)
          return h
  
      @FeatureNewKwargs('install_man', '0.47.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_man'])
      def func_install_man(self, node, args, kwargs):
          fargs = self.source_strings_to_files(args)
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          m = Man(fargs, kwargs)
          self.build.man.append(m)
          return m
  
      @FeatureNewKwargs('subdir', '0.44.0', ['if_found'])
      @permittedKwargs(permitted_kwargs['subdir'])
      def func_subdir(self, node, args, kwargs):
          self.validate_arguments(args, 1, [str])
          mesonlib.check_direntry_issues(args)
          if '..' in args[0]:
              raise InvalidArguments('Subdir contains ..')
          if self.subdir == '' and args[0] == self.subproject_dir:
              raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.')
          if self.subdir == '' and args[0].startswith('meson-'):
              raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().')
          for i in mesonlib.extract_as_list(kwargs, 'if_found'):
              if not hasattr(i, 'found_method'):
                  raise InterpreterException('Object used in if_found does not have a found method.')
              if not i.found_method([], {}):
                  return
          prev_subdir = self.subdir
          subdir = os.path.join(prev_subdir, args[0])
          if os.path.isabs(subdir):
              raise InvalidArguments('Subdir argument must be a relative path.')
          absdir = os.path.join(self.environment.get_source_dir(), subdir)
          symlinkless_dir = os.path.realpath(absdir)
          if symlinkless_dir in self.visited_subdirs:
              raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'
                                     % subdir)
          self.visited_subdirs[symlinkless_dir] = True
          self.subdir = subdir
          os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True)
          buildfilename = os.path.join(self.subdir, environment.build_filename)
          self.build_def_files.append(buildfilename)
          absname = os.path.join(self.environment.get_source_dir(), buildfilename)
          if not os.path.isfile(absname):
              self.subdir = prev_subdir
              raise InterpreterException('Non-existent build file {!r}'.format(buildfilename))
          with open(absname, encoding='utf8') as f:
              code = f.read()
          assert(isinstance(code, str))
          try:
              codeblock = mparser.Parser(code, absname).parse()
          except mesonlib.MesonException as me:
              me.file = absname
              raise me
          try:
              self.evaluate_codeblock(codeblock)
          except SubdirDoneRequest:
              pass
          self.subdir = prev_subdir
  
      def _get_kwarg_install_mode(self, kwargs):
          if kwargs.get('install_mode', None) is None:
              return None
          install_mode = []
          mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
          for m in mode:
              # We skip any arguments that are set to `false`
              if m is False:
                  m = None
              install_mode.append(m)
          if len(install_mode) > 3:
              raise InvalidArguments('Keyword argument install_mode takes at '
                                     'most 3 arguments.')
          if len(install_mode) > 0 and install_mode[0] is not None and \
             not isinstance(install_mode[0], str):
              raise InvalidArguments('Keyword argument install_mode requires the '
                                     'permissions arg to be a string or false')
          return FileMode(*install_mode)
  
      @FeatureNewKwargs('install_data', '0.46.0', ['rename'])
      @FeatureNewKwargs('install_data', '0.38.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_data'])
      def func_install_data(self, node, args, kwargs):
          kwsource = mesonlib.stringlistify(kwargs.get('sources', []))
          raw_sources = args + kwsource
          sources = []
          source_strings = []
          for s in raw_sources:
              if isinstance(s, mesonlib.File):
                  sources.append(s)
              elif isinstance(s, str):
                  source_strings.append(s)
              else:
                  raise InvalidArguments('Argument {!r} must be string or file.'.format(s))
          sources += self.source_strings_to_files(source_strings)
          install_dir = kwargs.get('install_dir', None)
          if not isinstance(install_dir, (str, type(None))):
              raise InvalidArguments('Keyword argument install_dir not a string.')
          install_mode = self._get_kwarg_install_mode(kwargs)
          rename = kwargs.get('rename', None)
          data = DataHolder(build.Data(sources, install_dir, install_mode, rename))
          self.build.data.append(data.held_object)
          return data
  
      @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories'])
      @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode'])
      @permittedKwargs(permitted_kwargs['install_subdir'])
      @stringArgs
      def func_install_subdir(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidArguments('Install_subdir requires exactly one argument.')
          subdir = args[0]
          if 'install_dir' not in kwargs:
              raise InvalidArguments('Missing keyword argument install_dir')
          install_dir = kwargs['install_dir']
          if not isinstance(install_dir, str):
              raise InvalidArguments('Keyword argument install_dir not a string.')
          if 'strip_directory' in kwargs:
              if not isinstance(kwargs['strip_directory'], bool):
                  raise InterpreterException('"strip_directory" keyword must be a boolean.')
              strip_directory = kwargs['strip_directory']
          else:
              strip_directory = False
          if 'exclude_files' in kwargs:
              exclude = extract_as_list(kwargs, 'exclude_files')
              for f in exclude:
                  if not isinstance(f, str):
                      raise InvalidArguments('Exclude argument not a string.')
                  elif os.path.isabs(f):
                      raise InvalidArguments('Exclude argument cannot be absolute.')
              exclude_files = set(exclude)
          else:
              exclude_files = set()
          if 'exclude_directories' in kwargs:
              exclude = extract_as_list(kwargs, 'exclude_directories')
              for d in exclude:
                  if not isinstance(d, str):
                      raise InvalidArguments('Exclude argument not a string.')
                  elif os.path.isabs(d):
                      raise InvalidArguments('Exclude argument cannot be absolute.')
              exclude_directories = set(exclude)
          else:
              exclude_directories = set()
          exclude = (exclude_files, exclude_directories)
          install_mode = self._get_kwarg_install_mode(kwargs)
          idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
          self.build.install_dirs.append(idir)
          return idir
  
      @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding'])
      @FeatureNewKwargs('configure_file', '0.46.0', ['format'])
      @FeatureNewKwargs('configure_file', '0.41.0', ['capture'])
      @FeatureNewKwargs('configure_file', '0.50.0', ['install'])
      @FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
      @permittedKwargs(permitted_kwargs['configure_file'])
      def func_configure_file(self, node, args, kwargs):
          if len(args) > 0:
              raise InterpreterException("configure_file takes only keyword arguments.")
          if 'output' not in kwargs:
              raise InterpreterException('Required keyword argument "output" not defined.')
          actions = set(['configuration', 'command', 'copy']).intersection(kwargs.keys())
          if len(actions) == 0:
              raise InterpreterException('Must specify an action with one of these '
                                         'keyword arguments: \'configuration\', '
                                         '\'command\', or \'copy\'.')
          elif len(actions) == 2:
              raise InterpreterException('Must not specify both {!r} and {!r} '
                                         'keyword arguments since they are '
                                         'mutually exclusive.'.format(*actions))
          elif len(actions) == 3:
              raise InterpreterException('Must specify one of {!r}, {!r}, and '
                                         '{!r} keyword arguments since they are '
                                         'mutually exclusive.'.format(*actions))
          if 'capture' in kwargs:
              if not isinstance(kwargs['capture'], bool):
                  raise InterpreterException('"capture" keyword must be a boolean.')
              if 'command' not in kwargs:
                  raise InterpreterException('"capture" keyword requires "command" keyword.')
  
          if 'format' in kwargs:
              fmt = kwargs['format']
              if not isinstance(fmt, str):
                  raise InterpreterException('"format" keyword must be a string.')
          else:
              fmt = 'meson'
  
          if fmt not in ('meson', 'cmake', 'cmake@'):
              raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".')
  
          if 'output_format' in kwargs:
              output_format = kwargs['output_format']
              if not isinstance(output_format, str):
                  raise InterpreterException('"output_format" keyword must be a string.')
          else:
              output_format = 'c'
  
          if output_format not in ('c', 'nasm'):
              raise InterpreterException('"format" possible values are "c" or "nasm".')
  
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InterpreterException('depfile file name must be a string')
          else:
              depfile = None
  
          # Validate input
          inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input'))
          inputs_abs = []
          for f in inputs:
              if isinstance(f, mesonlib.File):
                  inputs_abs.append(f.absolute_path(self.environment.source_dir,
                                                    self.environment.build_dir))
                  self.add_build_def_file(f)
              else:
                  raise InterpreterException('Inputs can only be strings or file objects')
          # Validate output
          output = kwargs['output']
          if not isinstance(output, str):
              raise InterpreterException('Output file name must be a string')
          if inputs_abs:
              values = mesonlib.get_filenames_templates_dict(inputs_abs, None)
              outputs = mesonlib.substitute_values([output], values)
              output = outputs[0]
              if depfile:
                  depfile = mesonlib.substitute_values([depfile], values)[0]
          ofile_rpath = os.path.join(self.subdir, output)
          if ofile_rpath in self.configure_file_outputs:
              mesonbuildfile = os.path.join(self.subdir, 'meson.build')
              current_call = "{}:{}".format(mesonbuildfile, self.current_lineno)
              first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath])
              mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call)
          else:
              self.configure_file_outputs[ofile_rpath] = self.current_lineno
          if os.path.dirname(output) != '':
              raise InterpreterException('Output file name must not contain a subdirectory.')
          (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
          ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname)
          # Perform the appropriate action
          if 'configuration' in kwargs:
              conf = kwargs['configuration']
              if isinstance(conf, dict):
                  FeatureNew('configure_file.configuration dictionary', '0.49.0').use(self.subproject)
                  conf = ConfigurationDataHolder(self.subproject, conf)
              elif not isinstance(conf, ConfigurationDataHolder):
                  raise InterpreterException('Argument "configuration" is not of type configuration_data')
              mlog.log('Configuring', mlog.bold(output), 'using configuration')
              if len(inputs) > 1:
                  raise InterpreterException('At most one input file can given in configuration mode')
              if inputs:
                  os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
                  file_encoding = kwargs.setdefault('encoding', 'utf-8')
                  missing_variables, confdata_useless = \
                      mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object,
                                            fmt, file_encoding)
                  if missing_variables:
                      var_list = ", ".join(map(repr, sorted(missing_variables)))
                      mlog.warning(
                          "The variable(s) %s in the input file '%s' are not "
                          "present in the given configuration data." % (
                              var_list, inputs[0]), location=node)
                  if confdata_useless:
                      ifbase = os.path.basename(inputs_abs[0])
                      mlog.warning('Got an empty configuration_data() object and found no '
                                   'substitutions in the input file {!r}. If you want to '
                                   'copy a file to the build dir, use the \'copy:\' keyword '
                                   'argument added in 0.47.0'.format(ifbase), location=node)
              else:
                  mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format)
              conf.mark_used()
          elif 'command' in kwargs:
              if len(inputs) > 1:
                  FeatureNew('multiple inputs in configure_file()', '0.52.0').use(self.subproject)
              # We use absolute paths for input and output here because the cwd
              # that the command is run from is 'unspecified', so it could change.
              # Currently it's builddir/subdir for in_builddir else srcdir/subdir.
              values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs])
              if depfile:
                  depfile = os.path.join(self.environment.get_scratch_dir(), depfile)
                  values['@DEPFILE@'] = depfile
              # Substitute @INPUT@, @OUTPUT@, etc here.
              cmd = mesonlib.substitute_values(kwargs['command'], values)
              mlog.log('Configuring', mlog.bold(output), 'with command')
              res = self.run_command_impl(node, cmd,  {}, True)
              if res.returncode != 0:
                  raise InterpreterException('Running configure command failed.\n%s\n%s' %
                                             (res.stdout, res.stderr))
              if 'capture' in kwargs and kwargs['capture']:
                  dst_tmp = ofile_abs + '~'
                  file_encoding = kwargs.setdefault('encoding', 'utf-8')
                  with open(dst_tmp, 'w', encoding=file_encoding) as f:
                      f.writelines(res.stdout)
                  if inputs_abs:
                      shutil.copymode(inputs_abs[0], dst_tmp)
                  mesonlib.replace_if_different(ofile_abs, dst_tmp)
              if depfile:
                  mlog.log('Reading depfile:', mlog.bold(depfile))
                  with open(depfile, 'r') as f:
                      df = DepFile(f.readlines())
                      deps = df.get_all_dependencies(ofile_fname)
                      for dep in deps:
                          self.add_build_def_file(dep)
  
          elif 'copy' in kwargs:
              if len(inputs_abs) != 1:
                  raise InterpreterException('Exactly one input file must be given in copy mode')
              os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
              shutil.copyfile(inputs_abs[0], ofile_abs)
              shutil.copystat(inputs_abs[0], ofile_abs)
          else:
              # Not reachable
              raise AssertionError
          # Install file if requested, we check for the empty string
          # for backwards compatibility. That was the behaviour before
          # 0.45.0 so preserve it.
          idir = kwargs.get('install_dir', '')
          if idir is False:
              idir = ''
              mlog.deprecation('Please use the new `install:` kwarg instead of passing '
                               '`false` to `install_dir:`', location=node)
          if not isinstance(idir, str):
              if isinstance(idir, list) and len(idir) == 0:
                  mlog.deprecation('install_dir: kwarg must be a string and not an empty array. '
                                   'Please use the install: kwarg to enable or disable installation. '
                                   'This will be a hard error in the next release.')
              else:
                  raise InterpreterException('"install_dir" must be a string')
          install = kwargs.get('install', idir != '')
          if not isinstance(install, bool):
              raise InterpreterException('"install" must be a boolean')
          if install:
              if not idir:
                  raise InterpreterException('"install_dir" must be specified '
                                             'when "install" in a configure_file '
                                             'is true')
              cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname)
              install_mode = self._get_kwarg_install_mode(kwargs)
              self.build.data.append(build.Data([cfile], idir, install_mode))
          return mesonlib.File.from_built_file(self.subdir, output)
  
      def extract_incdirs(self, kwargs):
          prospectives = unholder(extract_as_list(kwargs, 'include_directories'))
          result = []
          for p in prospectives:
              if isinstance(p, build.IncludeDirs):
                  result.append(p)
              elif isinstance(p, str):
                  result.append(self.build_incdir_object([p]).held_object)
              else:
                  raise InterpreterException('Include directory objects can only be created from strings or include directories.')
          return result
  
      @permittedKwargs(permitted_kwargs['include_directories'])
      @stringArgs
      def func_include_directories(self, node, args, kwargs):
          return self.build_incdir_object(args, kwargs.get('is_system', False))
  
      def build_incdir_object(self, incdir_strings, is_system=False):
          if not isinstance(is_system, bool):
              raise InvalidArguments('Is_system must be boolean.')
          src_root = self.environment.get_source_dir()
          build_root = self.environment.get_build_dir()
          absbase_src = os.path.join(src_root, self.subdir)
          absbase_build = os.path.join(build_root, self.subdir)
  
          for a in incdir_strings:
              if a.startswith(src_root):
                  raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use
  relative paths instead.
  
  To get include path to any directory relative to the current dir do
  
  incdir = include_directories(dirname)
  
  After this incdir will contain both the current source dir as well as the
  corresponding build dir. It can then be used in any subdirectory and
  Meson will take care of all the busywork to make paths work.
  
  Dirname can even be '.' to mark the current directory. Though you should
  remember that the current source and build directories are always
  put in the include directories by default so you only need to do
  include_directories('.') if you intend to use the result in a
  different subdirectory.
  ''')
              absdir_src = os.path.join(absbase_src, a)
              absdir_build = os.path.join(absbase_build, a)
              if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build):
                  raise InvalidArguments('Include dir %s does not exist.' % a)
          i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system))
          return i
  
      @permittedKwargs(permitted_kwargs['add_test_setup'])
      @stringArgs
      def func_add_test_setup(self, node, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Add_test_setup needs one argument for the setup name.')
          setup_name = args[0]
          if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None:
              raise InterpreterException('Setup name may only contain alphanumeric characters.')
          if ":" not in setup_name:
              setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name
          try:
              inp = unholder(extract_as_list(kwargs, 'exe_wrapper'))
              exe_wrapper = []
              for i in inp:
                  if isinstance(i, str):
                      exe_wrapper.append(i)
                  elif isinstance(i, dependencies.ExternalProgram):
                      if not i.found():
                          raise InterpreterException('Tried to use non-found executable.')
                      exe_wrapper += i.get_command()
                  else:
                      raise InterpreterException('Exe wrapper can only contain strings or external binaries.')
          except KeyError:
              exe_wrapper = None
          gdb = kwargs.get('gdb', False)
          if not isinstance(gdb, bool):
              raise InterpreterException('Gdb option must be a boolean')
          timeout_multiplier = kwargs.get('timeout_multiplier', 1)
          if not isinstance(timeout_multiplier, int):
              raise InterpreterException('Timeout multiplier must be a number.')
          is_default = kwargs.get('is_default', False)
          if not isinstance(is_default, bool):
              raise InterpreterException('is_default option must be a boolean')
          if is_default:
              if self.build.test_setup_default_name is not None:
                  raise InterpreterException('\'%s\' is already set as default. '
                                             'is_default can be set to true only once' % self.build.test_setup_default_name)
              self.build.test_setup_default_name = setup_name
          env = self.unpack_env_kwarg(kwargs)
          self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env)
  
      @permittedKwargs(permitted_kwargs['add_global_arguments'])
      @stringArgs
      def func_add_global_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_global_link_arguments'])
      @stringArgs
      def func_add_global_link_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_project_arguments'])
      @stringArgs
      def func_add_project_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs)
  
      @permittedKwargs(permitted_kwargs['add_project_link_arguments'])
      @stringArgs
      def func_add_project_link_arguments(self, node, args, kwargs):
          for_machine = self.machine_from_native_kwarg(kwargs)
          self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs)
  
      def warn_about_builtin_args(self, args):
          warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic')
          optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os')
          for arg in args:
              if arg in warnargs:
                  mlog.warning('Consider using the built-in warning_level option instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg in optargs:
                  mlog.warning('Consider using the built-in optimization level instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg == '-g':
                  mlog.warning('Consider using the built-in debug option instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg == '-pipe':
                  mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.",
                               location=self.current_node)
              elif arg.startswith('-fsanitize'):
                  mlog.warning('Consider using the built-in option for sanitizers instead of using "{}".'.format(arg),
                               location=self.current_node)
              elif arg.startswith('-std=') or arg.startswith('/std:'):
                  mlog.warning('Consider using the built-in option for language standard version instead of using "{}".'.format(arg),
                               location=self.current_node)
  
      def add_global_arguments(self, node, argsdict, args, kwargs):
          if self.is_subproject():
              msg = 'Function \'{}\' cannot be used in subprojects because ' \
                    'there is no way to make that reliable.\nPlease only call ' \
                    'this if is_subproject() returns false. Alternatively, ' \
                    'define a variable that\ncontains your language-specific ' \
                    'arguments and add it to the appropriate *_args kwarg ' \
                    'in each target.'.format(node.func_name)
              raise InvalidCode(msg)
          frozen = self.project_args_frozen or self.global_args_frozen
          self.add_arguments(node, argsdict, frozen, args, kwargs)
  
      def add_project_arguments(self, node, argsdict, args, kwargs):
          if self.subproject not in argsdict:
              argsdict[self.subproject] = {}
          self.add_arguments(node, argsdict[self.subproject],
                             self.project_args_frozen, args, kwargs)
  
      def add_arguments(self, node, argsdict, args_frozen, args, kwargs):
          if args_frozen:
              msg = 'Tried to use \'{}\' after a build target has been declared.\n' \
                    'This is not permitted. Please declare all ' \
                    'arguments before your targets.'.format(node.func_name)
              raise InvalidCode(msg)
  
          if 'language' not in kwargs:
              raise InvalidCode('Missing language definition in {}'.format(node.func_name))
  
          self.warn_about_builtin_args(args)
  
          for lang in mesonlib.stringlistify(kwargs['language']):
              lang = lang.lower()
              argsdict[lang] = argsdict.get(lang, []) + args
  
      @noKwargs
      @noArgsFlattening
      def func_environment(self, node, args, kwargs):
          if len(args) > 1:
              raise InterpreterException('environment takes only one optional positional arguments')
          elif len(args) == 1:
              FeatureNew('environment positional arguments', '0.52.0').use(self.subproject)
              initial_values = args[0]
              if not isinstance(initial_values, dict) and not isinstance(initial_values, list):
                  raise InterpreterException('environment first argument must be a dictionary or a list')
          else:
              initial_values = {}
          return EnvironmentVariablesHolder(initial_values)
  
      @stringArgs
      @noKwargs
      def func_join_paths(self, node, args, kwargs):
          return self.join_path_strings(args)
  
      def run(self):
          super().run()
          mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
          FeatureNew.report(self.subproject)
          FeatureDeprecated.report(self.subproject)
          if not self.is_subproject():
              self.print_extra_warnings()
          if self.subproject == '':
              self._print_summary()
  
      def print_extra_warnings(self):
          # TODO cross compilation
          for c in self.coredata.compilers.host.values():
              if c.get_id() == 'clang':
                  self.check_clang_asan_lundef()
                  break
  
      def check_clang_asan_lundef(self):
          if 'b_lundef' not in self.coredata.base_options:
              return
          if 'b_sanitize' not in self.coredata.base_options:
              return
          if (self.coredata.base_options['b_lundef'].value and
                  self.coredata.base_options['b_sanitize'].value != 'none'):
              mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef.
  This will probably not work.
  Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value),
                           location=self.current_node)
  
      def evaluate_subproject_info(self, path_from_source_root, subproject_dirname):
          depth = 0
          subproj_name = ''
          segs = PurePath(path_from_source_root).parts
          segs_spd = PurePath(subproject_dirname).parts
          while segs and segs[0] == segs_spd[0]:
              if len(segs_spd) == 1:
                  subproj_name = segs[1]
                  segs = segs[2:]
                  depth += 1
              else:
                  segs_spd = segs_spd[1:]
                  segs = segs[1:]
          return (depth, subproj_name)
  
      # Check that the indicated file is within the same subproject
      # as we currently are. This is to stop people doing
      # nasty things like:
      #
      # f = files('../../master_src/file.c')
      #
      # Note that this is validated only when the file
      # object is generated. The result can be used in a different
      # subproject than it is defined in (due to e.g. a
      # declare_dependency).
      def validate_within_subproject(self, subdir, fname):
          norm = os.path.normpath(os.path.join(subdir, fname))
          if os.path.isabs(norm):
              if not norm.startswith(self.environment.source_dir):
                  # Grabbing files outside the source tree is ok.
                  # This is for vendor stuff like:
                  #
                  # /opt/vendorsdk/src/file_with_license_restrictions.c
                  return
              norm = os.path.relpath(norm, self.environment.source_dir)
              assert(not os.path.isabs(norm))
          (num_sps, sproj_name) = self.evaluate_subproject_info(norm, self.subproject_dir)
          plain_filename = os.path.basename(norm)
          if num_sps == 0:
              if not self.is_subproject():
                  return
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
          if num_sps > 1:
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a nested subproject.' % plain_filename)
          if sproj_name != self.subproject_directory_name:
              raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename)
  
      def source_strings_to_files(self, sources):
          results = []
          mesonlib.check_direntry_issues(sources)
          if not isinstance(sources, list):
              sources = [sources]
          for s in sources:
              if isinstance(s, (mesonlib.File, GeneratedListHolder,
                                TargetHolder, CustomTargetIndexHolder,
                                GeneratedObjectsHolder)):
                  pass
              elif isinstance(s, str):
                  self.validate_within_subproject(self.subdir, s)
                  s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s)
              else:
                  raise InterpreterException('Source item is {!r} instead of '
                                             'string or File-type object'.format(s))
              results.append(s)
          return results
  
      def add_target(self, name, tobj):
          if name == '':
              raise InterpreterException('Target name must not be empty.')
          if name.strip() == '':
              raise InterpreterException('Target name must not consist only of whitespace.')
          if name.startswith('meson-'):
              raise InvalidArguments("Target names starting with 'meson-' are reserved "
                                     "for Meson's internal use. Please rename.")
          if name in coredata.forbidden_target_names:
              raise InvalidArguments("Target name '%s' is reserved for Meson's "
                                     "internal use. Please rename." % name)
          # To permit an executable and a shared library to have the
          # same name, such as "foo.exe" and "libfoo.a".
          idname = tobj.get_id()
          if idname in self.build.targets:
              raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name)
          self.build.targets[idname] = tobj
          if idname not in self.coredata.target_guids:
              self.coredata.target_guids[idname] = str(uuid.uuid4()).upper()
  
      @FeatureNew('both_libraries', '0.46.0')
      def build_both_libraries(self, node, args, kwargs):
          shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder)
  
          # Check if user forces non-PIC static library.
          pic = True
          if 'pic' in kwargs:
              pic = kwargs['pic']
          elif 'b_staticpic' in self.environment.coredata.base_options:
              pic = self.environment.coredata.base_options['b_staticpic'].value
  
          if pic:
              # Exclude sources from args and kwargs to avoid building them twice
              static_args = [args[0]]
              static_kwargs = kwargs.copy()
              static_kwargs['sources'] = []
              static_kwargs['objects'] = shared_holder.held_object.extract_all_objects()
          else:
              static_args = args
              static_kwargs = kwargs
  
          static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder)
  
          return BothLibrariesHolder(shared_holder, static_holder, self)
  
      def build_library(self, node, args, kwargs):
          default_library = self.coredata.get_builtin_option('default_library', self.subproject)
          if default_library == 'shared':
              return self.build_target(node, args, kwargs, SharedLibraryHolder)
          elif default_library == 'static':
              return self.build_target(node, args, kwargs, StaticLibraryHolder)
          elif default_library == 'both':
              return self.build_both_libraries(node, args, kwargs)
          else:
              raise InterpreterException('Unknown default_library value: %s.', default_library)
  
      def build_target(self, node, args, kwargs, targetholder):
          @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
          @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
          @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
          @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility'])
          def build_target_decorator_caller(self, node, args, kwargs):
              return True
  
          build_target_decorator_caller(self, node, args, kwargs)
  
          if not args:
              raise InterpreterException('Target does not have a name.')
          name, *sources = args
          for_machine = self.machine_from_native_kwarg(kwargs)
          if 'sources' in kwargs:
              sources += listify(kwargs['sources'])
          sources = self.source_strings_to_files(sources)
          objs = extract_as_list(kwargs, 'objects')
          kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
          kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
          if 'extra_files' in kwargs:
              ef = extract_as_list(kwargs, 'extra_files')
              kwargs['extra_files'] = self.source_strings_to_files(ef)
          self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources)
          if targetholder == ExecutableHolder:
              targetclass = build.Executable
          elif targetholder == SharedLibraryHolder:
              targetclass = build.SharedLibrary
          elif targetholder == SharedModuleHolder:
              targetclass = build.SharedModule
          elif targetholder == StaticLibraryHolder:
              targetclass = build.StaticLibrary
          elif targetholder == JarHolder:
              targetclass = build.Jar
          else:
              mlog.debug('Unknown target type:', str(targetholder))
              raise RuntimeError('Unreachable code')
          self.kwarg_strings_to_includedirs(kwargs)
  
          # Filter out kwargs from other target types. For example 'soversion'
          # passed to library() when default_library == 'static'.
          kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
  
          kwargs['include_directories'] = self.extract_incdirs(kwargs)
          target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
          target.project_version = self.project_version
  
          if not self.environment.machines.matches_build_machine(for_machine):
              self.add_cross_stdlib_info(target)
          l = targetholder(target, self)
          self.add_target(name, l.held_object)
          self.project_args_frozen = True
          return l
  
      def kwarg_strings_to_includedirs(self, kwargs):
          if 'd_import_dirs' in kwargs:
              items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
              cleaned_items = []
              for i in items:
                  if isinstance(i, str):
                      # BW compatibility. This was permitted so we must support it
                      # for a few releases so people can transition to "correct"
                      # path declarations.
                      if os.path.normpath(i).startswith(self.environment.get_source_dir()):
                          mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead.
  This will become a hard error in the future.''', location=self.current_node)
                          i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir))
                          i = self.build_incdir_object([i])
                  cleaned_items.append(i)
              kwargs['d_import_dirs'] = cleaned_items
  
      def get_used_languages(self, target):
          result = {}
          for i in target.sources:
              # TODO other platforms
              for lang, c in self.coredata.compilers.host.items():
                  if c.can_compile(i):
                      result[lang] = True
                      break
          return result
  
      def add_cross_stdlib_info(self, target):
          if target.for_machine != MachineChoice.HOST:
              return
          for l in self.get_used_languages(target):
              props = self.environment.properties.host
              if props.has_stdlib(l) \
                      and self.subproject != props.get_stdlib(l)[0]:
                  target.add_deps(self.build.stdlibs.host[l])
  
      def check_sources_exist(self, subdir, sources):
          for s in sources:
              if not isinstance(s, str):
                  continue # This means a generated source and they always exist.
              fname = os.path.join(subdir, s)
              if not os.path.isfile(fname):
                  raise InterpreterException('Tried to add non-existing source file %s.' % s)
  
      # Only permit object extraction from the same subproject
      def validate_extraction(self, buildtarget: InterpreterObject) -> None:
          if not self.subdir.startswith(self.subproject_dir):
              if buildtarget.subdir.startswith(self.subproject_dir):
                  raise InterpreterException('Tried to extract objects from a subproject target.')
          else:
              if not buildtarget.subdir.startswith(self.subproject_dir):
                  raise InterpreterException('Tried to extract objects from the main project from a subproject.')
              if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
                  raise InterpreterException('Tried to extract objects from a different subproject.')
  
      def is_subproject(self):
          return self.subproject != ''
  
      @noKwargs
      @noArgsFlattening
      def func_set_variable(self, node, args, kwargs):
          if len(args) != 2:
              raise InvalidCode('Set_variable takes two arguments.')
          varname, value = args
          self.set_variable(varname, value)
  
      @noKwargs
      @noArgsFlattening
      def func_get_variable(self, node, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InvalidCode('Get_variable takes one or two arguments.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('First argument must be a string.')
          try:
              return self.variables[varname]
          except KeyError:
              pass
          if len(args) == 2:
              return args[1]
          raise InterpreterException('Tried to get unknown variable "%s".' % varname)
  
      @stringArgs
      @noKwargs
      def func_is_variable(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Is_variable takes two arguments.')
          varname = args[0]
          return varname in self.variables
  
      @staticmethod
      def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice:
          native = kwargs.get('native', False)
          if not isinstance(native, bool):
              raise InvalidArguments('Argument to "native" must be a boolean.')
          return MachineChoice.BUILD if native else MachineChoice.HOST
  
      @FeatureNew('is_disabler', '0.52.0')
      @noKwargs
      def func_is_disabler(self, node, args, kwargs):
          if len(args) != 1:
              raise InvalidCode('Is_disabler takes one argument.')
          varname = args[0]
          return isinstance(varname, Disabler)
  
files: mesonbuild/interpreter.py
files: # Copyright 2014-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  """This is a helper script for IDE developers. It allows you to
  extract information such as list of targets, files, compiler flags,
  tests and so on. All output is in JSON for simple parsing.
  
  Currently only works for the Ninja backend. Others use generated
  project files and don't need this info."""
  
  import json
  from . import build, coredata as cdata
  from . import mesonlib
  from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator
  from . import mlog
  from .backend import backends
  from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode
  from .interpreter import Interpreter
  from pathlib import PurePath
  import typing as T
  import os
  
  def get_meson_info_file(info_dir: str) -> str:
      return os.path.join(info_dir, 'meson-info.json')
  
  def get_meson_introspection_version() -> str:
      return '1.0.0'
  
  def get_meson_introspection_required_version() -> T.List[str]:
      return ['>=1.0', '<2.0']
  
  class IntroCommand:
      def __init__(self,
                   desc: str,
                   func: T.Optional[T.Callable[[], T.Union[dict, list]]] = None,
                   no_bd: T.Optional[T.Callable[[IntrospectionInterpreter], T.Union[dict, list]]] = None) -> None:
          self.desc = desc + '.'
          self.func = func
          self.no_bd = no_bd
  
  def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None,
                                    builddata: T.Optional[build.Build] = None,
                                    backend: T.Optional[backends.Backend] = None,
                                    sourcedir: T.Optional[str] = None) -> T.Dict[str, IntroCommand]:
      if backend and builddata:
          benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks())
          testdata = backend.create_test_serialisation(builddata.get_tests())
          installdata = backend.create_install_data()
          interpreter = backend.interpreter
      else:
          benchmarkdata = testdata = installdata = None
  
      return {
          'benchmarks': IntroCommand('List all benchmarks', func=lambda: list_benchmarks(benchmarkdata)),
          'buildoptions': IntroCommand('List all build options', func=lambda: list_buildoptions(coredata), no_bd=list_buildoptions_from_source),
          'buildsystem_files': IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata, interpreter)),
          'dependencies': IntroCommand('List external dependencies', func=lambda: list_deps(coredata), no_bd=list_deps_from_source),
          'scan_dependencies': IntroCommand('Scan for dependencies used in the meson.build file', no_bd=list_deps_from_source),
          'installed': IntroCommand('List all installed files and directories', func=lambda: list_installed(installdata)),
          'projectinfo': IntroCommand('Information about projects', func=lambda: list_projinfo(builddata), no_bd=list_projinfo_from_source),
          'targets': IntroCommand('List top level targets', func=lambda: list_targets(builddata, installdata, backend), no_bd=list_targets_from_source),
          'tests': IntroCommand('List all unit tests', func=lambda: list_tests(testdata)),
      }
  
  def add_arguments(parser):
      intro_types = get_meson_introspection_types()
      for key, val in intro_types.items():
          flag = '--' + key.replace('_', '-')
          parser.add_argument(flag, action='store_true', dest=key, default=False, help=val.desc)
  
      parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja',
                          help='The backend to use for the --buildoptions introspection.')
      parser.add_argument('-a', '--all', action='store_true', dest='all', default=False,
                          help='Print all available information.')
      parser.add_argument('-i', '--indent', action='store_true', dest='indent', default=False,
                          help='Enable pretty printed JSON.')
      parser.add_argument('-f', '--force-object-output', action='store_true', dest='force_dict', default=False,
                          help='Always use the new JSON format for multiple entries (even for 0 and 1 introspection commands)')
      parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
  
  def list_installed(installdata):
      res = {}
      if installdata is not None:
          for t in installdata.targets:
              res[os.path.join(installdata.build_dir, t.fname)] = \
                  os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
              for alias in t.aliases.keys():
                  res[os.path.join(installdata.build_dir, alias)] = \
                      os.path.join(installdata.prefix, t.outdir, os.path.basename(alias))
          for path, installpath, _ in installdata.data:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installdir, _ in installdata.headers:
              res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
          for path, installpath, _ in installdata.man:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installpath, _, _ in installdata.install_subdirs:
              res[path] = os.path.join(installdata.prefix, installpath)
      return res
  
  def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
      tlist = []  # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]
      for i in intr.targets:
          sources = []  # type: T.List[str]
          for n in i['sources']:
              args = []  # type: T.List[BaseNode]
              if isinstance(n, FunctionNode):
                  args = list(n.args.arguments)
                  if n.func_name in build_target_functions:
                      args.pop(0)
              elif isinstance(n, ArrayNode):
                  args = n.args.arguments
              elif isinstance(n, ArgumentNode):
                  args = n.arguments
              for j in args:
                  if isinstance(j, StringNode):
                      assert isinstance(j.value, str)
                      sources += [j.value]
                  elif isinstance(j, str):
                      sources += [j]
  
          tlist += [{
              'name': i['name'],
              'id': i['id'],
              'type': i['type'],
              'defined_in': i['defined_in'],
              'filename': [os.path.join(i['subdir'], x) for x in i['outputs']],
              'build_by_default': i['build_by_default'],
              'target_sources': [{
                  'language': 'unknown',
                  'compiler': [],
                  'parameters': [],
                  'sources': [os.path.normpath(os.path.join(os.path.abspath(intr.source_root), i['subdir'], x)) for x in sources],
                  'generated_sources': []
              }],
              'subproject': None, # Subprojects are not supported
              'installed': i['installed']
          }]
  
      return tlist
  
  def list_targets(builddata: build.Build, installdata, backend: backends.Backend) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
      tlist = []  # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]
      build_dir = builddata.environment.get_build_dir()
      src_dir = builddata.environment.get_source_dir()
  
      # Fast lookup table for installation files
      install_lookuptable = {}
      for i in installdata.targets:
          out = [os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))]
          out += [os.path.join(installdata.prefix, i.outdir, os.path.basename(x)) for x in i.aliases]
          install_lookuptable[os.path.basename(i.fname)] = [str(PurePath(x)) for x in out]
  
      for (idname, target) in builddata.get_targets().items():
          if not isinstance(target, build.Target):
              raise RuntimeError('The target object in `builddata.get_targets()` is not of type `build.Target`. Please file a bug with this error message.')
  
          t = {
              'name': target.get_basename(),
              'id': idname,
              'type': target.get_typename(),
              'defined_in': os.path.normpath(os.path.join(src_dir, target.subdir, 'meson.build')),
              'filename': [os.path.join(build_dir, target.subdir, x) for x in target.get_outputs()],
              'build_by_default': target.build_by_default,
              'target_sources': backend.get_introspection_data(idname, target),
              'subproject': target.subproject or None
          }
  
          if installdata and target.should_install():
              t['installed'] = True
              t['install_filename'] = [install_lookuptable.get(x, [None]) for x in target.get_outputs()]
              t['install_filename'] = [x for sublist in t['install_filename'] for x in sublist]  # flatten the list
          else:
              t['installed'] = False
          tlist.append(t)
      return tlist
  
  def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
      subprojects = [i['name'] for i in intr.project_data['subprojects']]
      return list_buildoptions(intr.coredata, subprojects)
  
  def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
      optlist = []  # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]
  
      dir_option_names = ['bindir',
                          'datadir',
                          'includedir',
                          'infodir',
                          'libdir',
                          'libexecdir',
                          'localedir',
                          'localstatedir',
                          'mandir',
                          'prefix',
                          'sbindir',
                          'sharedstatedir',
                          'sysconfdir']
      test_option_names = ['errorlogs',
                           'stdsplit']
      core_option_names = [k for k in coredata.builtins if k not in dir_option_names + test_option_names]
  
      dir_options = {k: o for k, o in coredata.builtins.items() if k in dir_option_names}
      test_options = {k: o for k, o in coredata.builtins.items() if k in test_option_names}
      core_options = {k: o for k, o in coredata.builtins.items() if k in core_option_names}
  
      if subprojects:
          # Add per subproject built-in options
          sub_core_options = {}
          for sub in subprojects:
              for k, o in core_options.items():
                  if o.yielding:
                      continue
                  sub_core_options[sub + ':' + k] = o
          core_options.update(sub_core_options)
  
      def add_keys(options: T.Dict[str, cdata.UserOption], section: str, machine: str = 'any') -> None:
          for key in sorted(options.keys()):
              opt = options[key]
              optdict = {'name': key, 'value': opt.value, 'section': section, 'machine': machine}
              if isinstance(opt, cdata.UserStringOption):
                  typestr = 'string'
              elif isinstance(opt, cdata.UserBooleanOption):
                  typestr = 'boolean'
              elif isinstance(opt, cdata.UserComboOption):
                  optdict['choices'] = opt.choices
                  typestr = 'combo'
              elif isinstance(opt, cdata.UserIntegerOption):
                  typestr = 'integer'
              elif isinstance(opt, cdata.UserArrayOption):
                  typestr = 'array'
              else:
                  raise RuntimeError("Unknown option type")
              optdict['type'] = typestr
              optdict['description'] = opt.description
              optlist.append(optdict)
  
      add_keys(core_options, 'core')
      add_keys(coredata.builtins_per_machine.host, 'core', machine='host')
      add_keys(
          {'build.' + k: o for k, o in coredata.builtins_per_machine.build.items()},
          'core',
          machine='build',
      )
      add_keys(coredata.backend_options, 'backend')
      add_keys(coredata.base_options, 'base')
      add_keys(
          dict(coredata.flatten_lang_iterator(coredata.compiler_options.host.items())),
          'compiler',
          machine='host',
      )
      add_keys(
          {
              'build.' + k: o for k, o in
              coredata.flatten_lang_iterator(coredata.compiler_options.build.items())
          },
          'compiler',
          machine='build',
      )
      add_keys(dir_options, 'directory')
      add_keys(coredata.user_options, 'user')
      add_keys(test_options, 'test')
      return optlist
  
  def find_buildsystem_files_list(src_dir) -> T.List[str]:
      # I feel dirty about this. But only slightly.
      filelist = []  # type: T.List[str]
      for root, _, files in os.walk(src_dir):
          for f in files:
              if f == 'meson.build' or f == 'meson_options.txt':
                  filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
      return filelist
  
  def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]:
      src_dir = builddata.environment.get_source_dir()
      filelist = interpreter.get_build_def_files()
      filelist = [PurePath(src_dir, x).as_posix() for x in filelist]
      return filelist
  
  def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, bool]]]
      for i in intr.dependencies:
          keys = [
              'name',
              'required',
              'version',
              'has_fallback',
              'conditional',
          ]
          result += [{k: v for k, v in i.items() if k in keys}]
      return result
  
  def list_deps(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[str, T.List[str]]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, T.List[str]]]]
      for d in coredata.deps.host.values():
          if d.found():
              result += [{'name': d.name,
                          'version': d.get_version(),
                          'compile_args': d.get_compile_args(),
                          'link_args': d.get_link_args()}]
      return result
  
  def get_test_list(testdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]
      for t in testdata:
          to = {}
          if isinstance(t.fname, str):
              fname = [t.fname]
          else:
              fname = t.fname
          to['cmd'] = fname + t.cmd_args
          if isinstance(t.env, build.EnvironmentVariables):
              to['env'] = t.env.get_env({})
          else:
              to['env'] = t.env
          to['name'] = t.name
          to['workdir'] = t.workdir
          to['timeout'] = t.timeout
          to['suite'] = t.suite
          to['is_parallel'] = t.is_parallel
          to['priority'] = t.priority
          to['protocol'] = str(t.protocol)
          result.append(to)
      return result
  
  def list_tests(testdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      return get_test_list(testdata)
  
  def list_benchmarks(benchdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      return get_test_list(benchdata)
  
  def list_projinfo(builddata: build.Build) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]:
      result = {'version': builddata.project_version,
                'descriptive_name': builddata.project_name,
                'subproject_dir': builddata.subproject_dir}
      subprojects = []
      for k, v in builddata.subprojects.items():
          c = {'name': k,
               'version': v,
               'descriptive_name': builddata.projects.get(k)}
          subprojects.append(c)
      result['subprojects'] = subprojects
      return result
  
  def list_projinfo_from_source(intr: IntrospectionInterpreter) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]:
      sourcedir = intr.source_root
      files = find_buildsystem_files_list(sourcedir)
      files = [os.path.normpath(x) for x in files]
  
      for i in intr.project_data['subprojects']:
          basedir = os.path.join(intr.subproject_dir, i['name'])
          i['buildsystem_files'] = [x for x in files if x.startswith(basedir)]
          files = [x for x in files if not x.startswith(basedir)]
  
      intr.project_data['buildsystem_files'] = files
      intr.project_data['subproject_dir'] = intr.subproject_dir
      return intr.project_data
  
  def print_results(options, results: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], indent: int) -> int:
      if not results and not options.force_dict:
          print('No command specified')
          return 1
      elif len(results) == 1 and not options.force_dict:
          # Make to keep the existing output format for a single option
          print(json.dumps(results[0][1], indent=indent))
      else:
          out = {}
          for i in results:
              out[i[0]] = i[1]
          print(json.dumps(out, indent=indent))
      return 0
  
  def run(options) -> int:
      datadir = 'meson-private'
      infodir = 'meson-info'
      if options.builddir is not None:
          datadir = os.path.join(options.builddir, datadir)
          infodir = os.path.join(options.builddir, infodir)
      indent = 4 if options.indent else None
      results = []  # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
      sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11]
      intro_types = get_meson_introspection_types(sourcedir=sourcedir)
  
      if 'meson.build' in [os.path.basename(options.builddir), options.builddir]:
          # Make sure that log entries in other parts of meson don't interfere with the JSON output
          mlog.disable()
          backend = backends.get_backend_from_name(options.backend)
          intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()])
          intr.analyze()
          # Re-enable logging just in case
          mlog.enable()
          for key, val in intro_types.items():
              if (not options.all and not getattr(options, key, False)) or not val.no_bd:
                  continue
              results += [(key, val.no_bd(intr))]
          return print_results(options, results, indent)
  
      infofile = get_meson_info_file(infodir)
      if not os.path.isdir(datadir) or not os.path.isdir(infodir) or not os.path.isfile(infofile):
          print('Current directory is not a meson build directory.\n'
                'Please specify a valid build dir or change the working directory to it.\n'
                'It is also possible that the build directory was generated with an old\n'
                'meson version. Please regenerate it in this case.')
          return 1
  
      with open(infofile, 'r') as fp:
          raw = json.load(fp)
          intro_vers = raw.get('introspection', {}).get('version', {}).get('full', '0.0.0')
  
      vers_to_check = get_meson_introspection_required_version()
      for i in vers_to_check:
          if not mesonlib.version_compare(intro_vers, i):
              print('Introspection version {} is not supported. '
                    'The required version is: {}'
                    .format(intro_vers, ' and '.join(vers_to_check)))
              return 1
  
      # Extract introspection information from JSON
      for i in intro_types.keys():
          if not intro_types[i].func:
              continue
          if not options.all and not getattr(options, i, False):
              continue
          curr = os.path.join(infodir, 'intro-{}.json'.format(i))
          if not os.path.isfile(curr):
              print('Introspection file {} does not exist.'.format(curr))
              return 1
          with open(curr, 'r') as fp:
              results += [(i, json.load(fp))]
  
      return print_results(options, results, indent)
  
  updated_introspection_files = []  # type: T.List[str]
  
  def write_intro_info(intro_info: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], info_dir: str) -> None:
      global updated_introspection_files
      for i in intro_info:
          out_file = os.path.join(info_dir, 'intro-{}.json'.format(i[0]))
          tmp_file = os.path.join(info_dir, 'tmp_dump.json')
          with open(tmp_file, 'w') as fp:
              json.dump(i[1], fp)
              fp.flush() # Not sure if this is needed
          os.replace(tmp_file, out_file)
          updated_introspection_files += [i[0]]
  
  def generate_introspection_file(builddata: build.Build, backend: backends.Backend) -> None:
      coredata = builddata.environment.get_coredata()
      intro_types = get_meson_introspection_types(coredata=coredata, builddata=builddata, backend=backend)
      intro_info = []  # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
  
      for key, val in intro_types.items():
          if not val.func:
              continue
          intro_info += [(key, val.func())]
  
      write_intro_info(intro_info, builddata.environment.info_dir)
  
  def update_build_options(coredata: cdata.CoreData, info_dir) -> None:
      intro_info = [
          ('buildoptions', list_buildoptions(coredata))
      ]
  
      write_intro_info(intro_info, info_dir)
  
  def split_version_string(version: str) -> T.Dict[str, T.Union[str, int]]:
      vers_list = version.split('.')
      return {
          'full': version,
          'major': int(vers_list[0] if len(vers_list) > 0 else 0),
          'minor': int(vers_list[1] if len(vers_list) > 1 else 0),
          'patch': int(vers_list[2] if len(vers_list) > 2 else 0)
      }
  
  def write_meson_info_file(builddata: build.Build, errors: list, build_files_updated: bool = False) -> None:
      global updated_introspection_files
      info_dir = builddata.environment.info_dir
      info_file = get_meson_info_file(info_dir)
      intro_types = get_meson_introspection_types()
      intro_info = {}
  
      for i in intro_types.keys():
          if not intro_types[i].func:
              continue
          intro_info[i] = {
              'file': 'intro-{}.json'.format(i),
              'updated': i in updated_introspection_files
          }
  
      info_data = {
          'meson_version': split_version_string(cdata.version),
          'directories': {
              'source': builddata.environment.get_source_dir(),
              'build': builddata.environment.get_build_dir(),
              'info': info_dir,
          },
          'introspection': {
              'version': split_version_string(get_meson_introspection_version()),
              'information': intro_info,
          },
          'build_files_updated': build_files_updated,
      }
  
      if errors:
          info_data['error'] = True
          info_data['error_list'] = [x if isinstance(x, str) else str(x) for x in errors]
      else:
          info_data['error'] = False
  
      # Write the data to disc
      tmp_file = os.path.join(info_dir, 'tmp_dump.json')
      with open(tmp_file, 'w') as fp:
          json.dump(info_data, fp)
          fp.flush()
      os.replace(tmp_file, info_file)
  
files: mesonbuild/mintro.py
files: # Copyright 2014-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  """This is a helper script for IDE developers. It allows you to
  extract information such as list of targets, files, compiler flags,
  tests and so on. All output is in JSON for simple parsing.
  
  Currently only works for the Ninja backend. Others use generated
  project files and don't need this info."""
  
  import json
  from . import build, coredata as cdata
  from . import mesonlib
  from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator
  from . import mlog
  from .backend import backends
  from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode
  from .interpreter import Interpreter
  from pathlib import PurePath
  import typing as T
  import os
  
  def get_meson_info_file(info_dir: str) -> str:
      return os.path.join(info_dir, 'meson-info.json')
  
  def get_meson_introspection_version() -> str:
      return '1.0.0'
  
  def get_meson_introspection_required_version() -> T.List[str]:
      return ['>=1.0', '<2.0']
  
  class IntroCommand:
      def __init__(self,
                   desc: str,
                   func: T.Optional[T.Callable[[], T.Union[dict, list]]] = None,
                   no_bd: T.Optional[T.Callable[[IntrospectionInterpreter], T.Union[dict, list]]] = None) -> None:
          self.desc = desc + '.'
          self.func = func
          self.no_bd = no_bd
  
  def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None,
                                    builddata: T.Optional[build.Build] = None,
                                    backend: T.Optional[backends.Backend] = None,
                                    sourcedir: T.Optional[str] = None) -> T.Dict[str, IntroCommand]:
      if backend and builddata:
          benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks())
          testdata = backend.create_test_serialisation(builddata.get_tests())
          installdata = backend.create_install_data()
          interpreter = backend.interpreter
      else:
          benchmarkdata = testdata = installdata = None
  
      return {
          'benchmarks': IntroCommand('List all benchmarks', func=lambda: list_benchmarks(benchmarkdata)),
          'buildoptions': IntroCommand('List all build options', func=lambda: list_buildoptions(coredata), no_bd=list_buildoptions_from_source),
          'buildsystem_files': IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata, interpreter)),
          'dependencies': IntroCommand('List external dependencies', func=lambda: list_deps(coredata), no_bd=list_deps_from_source),
          'scan_dependencies': IntroCommand('Scan for dependencies used in the meson.build file', no_bd=list_deps_from_source),
          'installed': IntroCommand('List all installed files and directories', func=lambda: list_installed(installdata)),
          'projectinfo': IntroCommand('Information about projects', func=lambda: list_projinfo(builddata), no_bd=list_projinfo_from_source),
          'targets': IntroCommand('List top level targets', func=lambda: list_targets(builddata, installdata, backend), no_bd=list_targets_from_source),
          'tests': IntroCommand('List all unit tests', func=lambda: list_tests(testdata)),
      }
  
  def add_arguments(parser):
      intro_types = get_meson_introspection_types()
      for key, val in intro_types.items():
          flag = '--' + key.replace('_', '-')
          parser.add_argument(flag, action='store_true', dest=key, default=False, help=val.desc)
  
      parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja',
                          help='The backend to use for the --buildoptions introspection.')
      parser.add_argument('-a', '--all', action='store_true', dest='all', default=False,
                          help='Print all available information.')
      parser.add_argument('-i', '--indent', action='store_true', dest='indent', default=False,
                          help='Enable pretty printed JSON.')
      parser.add_argument('-f', '--force-object-output', action='store_true', dest='force_dict', default=False,
                          help='Always use the new JSON format for multiple entries (even for 0 and 1 introspection commands)')
      parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
  
  def list_installed(installdata):
      res = {}
      if installdata is not None:
          for t in installdata.targets:
              res[os.path.join(installdata.build_dir, t.fname)] = \
                  os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
              for alias in t.aliases.keys():
                  res[os.path.join(installdata.build_dir, alias)] = \
                      os.path.join(installdata.prefix, t.outdir, os.path.basename(alias))
          for path, installpath, _ in installdata.data:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installdir, _ in installdata.headers:
              res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
          for path, installpath, _ in installdata.man:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installpath, _, _ in installdata.install_subdirs:
              res[path] = os.path.join(installdata.prefix, installpath)
      return res
  
  def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
      tlist = []  # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]
      for i in intr.targets:
          sources = []  # type: T.List[str]
          for n in i['sources']:
              args = []  # type: T.List[BaseNode]
              if isinstance(n, FunctionNode):
                  args = list(n.args.arguments)
                  if n.func_name in build_target_functions:
                      args.pop(0)
              elif isinstance(n, ArrayNode):
                  args = n.args.arguments
              elif isinstance(n, ArgumentNode):
                  args = n.arguments
              for j in args:
                  if isinstance(j, StringNode):
                      assert isinstance(j.value, str)
                      sources += [j.value]
                  elif isinstance(j, str):
                      sources += [j]
  
          tlist += [{
              'name': i['name'],
              'id': i['id'],
              'type': i['type'],
              'defined_in': i['defined_in'],
              'filename': [os.path.join(i['subdir'], x) for x in i['outputs']],
              'build_by_default': i['build_by_default'],
              'target_sources': [{
                  'language': 'unknown',
                  'compiler': [],
                  'parameters': [],
                  'sources': [os.path.normpath(os.path.join(os.path.abspath(intr.source_root), i['subdir'], x)) for x in sources],
                  'generated_sources': []
              }],
              'subproject': None, # Subprojects are not supported
              'installed': i['installed']
          }]
  
      return tlist
  
  def list_targets(builddata: build.Build, installdata, backend: backends.Backend) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
      tlist = []  # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]
      build_dir = builddata.environment.get_build_dir()
      src_dir = builddata.environment.get_source_dir()
  
      # Fast lookup table for installation files
      install_lookuptable = {}
      for i in installdata.targets:
          out = [os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))]
          out += [os.path.join(installdata.prefix, i.outdir, os.path.basename(x)) for x in i.aliases]
          install_lookuptable[os.path.basename(i.fname)] = [str(PurePath(x)) for x in out]
  
      for (idname, target) in builddata.get_targets().items():
          if not isinstance(target, build.Target):
              raise RuntimeError('The target object in `builddata.get_targets()` is not of type `build.Target`. Please file a bug with this error message.')
  
          t = {
              'name': target.get_basename(),
              'id': idname,
              'type': target.get_typename(),
              'defined_in': os.path.normpath(os.path.join(src_dir, target.subdir, 'meson.build')),
              'filename': [os.path.join(build_dir, target.subdir, x) for x in target.get_outputs()],
              'build_by_default': target.build_by_default,
              'target_sources': backend.get_introspection_data(idname, target),
              'subproject': target.subproject or None
          }
  
          if installdata and target.should_install():
              t['installed'] = True
              t['install_filename'] = [install_lookuptable.get(x, [None]) for x in target.get_outputs()]
              t['install_filename'] = [x for sublist in t['install_filename'] for x in sublist]  # flatten the list
          else:
              t['installed'] = False
          tlist.append(t)
      return tlist
  
  def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
      subprojects = [i['name'] for i in intr.project_data['subprojects']]
      return list_buildoptions(intr.coredata, subprojects)
  
  def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
      optlist = []  # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]
  
      dir_option_names = ['bindir',
                          'datadir',
                          'includedir',
                          'infodir',
                          'libdir',
                          'libexecdir',
                          'localedir',
                          'localstatedir',
                          'mandir',
                          'prefix',
                          'sbindir',
                          'sharedstatedir',
                          'sysconfdir']
      test_option_names = ['errorlogs',
                           'stdsplit']
      core_option_names = [k for k in coredata.builtins if k not in dir_option_names + test_option_names]
  
      dir_options = {k: o for k, o in coredata.builtins.items() if k in dir_option_names}
      test_options = {k: o for k, o in coredata.builtins.items() if k in test_option_names}
      core_options = {k: o for k, o in coredata.builtins.items() if k in core_option_names}
  
      if subprojects:
          # Add per subproject built-in options
          sub_core_options = {}
          for sub in subprojects:
              for k, o in core_options.items():
                  if o.yielding:
                      continue
                  sub_core_options[sub + ':' + k] = o
          core_options.update(sub_core_options)
  
      def add_keys(options: T.Dict[str, cdata.UserOption], section: str, machine: str = 'any') -> None:
          for key in sorted(options.keys()):
              opt = options[key]
              optdict = {'name': key, 'value': opt.value, 'section': section, 'machine': machine}
              if isinstance(opt, cdata.UserStringOption):
                  typestr = 'string'
              elif isinstance(opt, cdata.UserBooleanOption):
                  typestr = 'boolean'
              elif isinstance(opt, cdata.UserComboOption):
                  optdict['choices'] = opt.choices
                  typestr = 'combo'
              elif isinstance(opt, cdata.UserIntegerOption):
                  typestr = 'integer'
              elif isinstance(opt, cdata.UserArrayOption):
                  typestr = 'array'
              else:
                  raise RuntimeError("Unknown option type")
              optdict['type'] = typestr
              optdict['description'] = opt.description
              optlist.append(optdict)
  
      add_keys(core_options, 'core')
      add_keys(coredata.builtins_per_machine.host, 'core', machine='host')
      add_keys(
          {'build.' + k: o for k, o in coredata.builtins_per_machine.build.items()},
          'core',
          machine='build',
      )
      add_keys(coredata.backend_options, 'backend')
      add_keys(coredata.base_options, 'base')
      add_keys(
          dict(coredata.flatten_lang_iterator(coredata.compiler_options.host.items())),
          'compiler',
          machine='host',
      )
      add_keys(
          {
              'build.' + k: o for k, o in
              coredata.flatten_lang_iterator(coredata.compiler_options.build.items())
          },
          'compiler',
          machine='build',
      )
      add_keys(dir_options, 'directory')
      add_keys(coredata.user_options, 'user')
      add_keys(test_options, 'test')
      return optlist
  
  def find_buildsystem_files_list(src_dir) -> T.List[str]:
      # I feel dirty about this. But only slightly.
      filelist = []  # type: T.List[str]
      for root, _, files in os.walk(src_dir):
          for f in files:
              if f == 'meson.build' or f == 'meson_options.txt':
                  filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
      return filelist
  
  def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]:
      src_dir = builddata.environment.get_source_dir()
      filelist = interpreter.get_build_def_files()
      filelist = [PurePath(src_dir, x).as_posix() for x in filelist]
      return filelist
  
  def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, bool]]]
      for i in intr.dependencies:
          keys = [
              'name',
              'required',
              'version',
              'has_fallback',
              'conditional',
          ]
          result += [{k: v for k, v in i.items() if k in keys}]
      return result
  
  def list_deps(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[str, T.List[str]]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, T.List[str]]]]
      for d in coredata.deps.host.values():
          if d.found():
              result += [{'name': d.name,
                          'version': d.get_version(),
                          'compile_args': d.get_compile_args(),
                          'link_args': d.get_link_args()}]
      return result
  
  def get_test_list(testdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      result = []  # type: T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]
      for t in testdata:
          to = {}
          if isinstance(t.fname, str):
              fname = [t.fname]
          else:
              fname = t.fname
          to['cmd'] = fname + t.cmd_args
          if isinstance(t.env, build.EnvironmentVariables):
              to['env'] = t.env.get_env({})
          else:
              to['env'] = t.env
          to['name'] = t.name
          to['workdir'] = t.workdir
          to['timeout'] = t.timeout
          to['suite'] = t.suite
          to['is_parallel'] = t.is_parallel
          to['priority'] = t.priority
          to['protocol'] = t.protocol
          result.append(to)
      return result
  
  def list_tests(testdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      return get_test_list(testdata)
  
  def list_benchmarks(benchdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
      return get_test_list(benchdata)
  
  def list_projinfo(builddata: build.Build) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]:
      result = {'version': builddata.project_version,
                'descriptive_name': builddata.project_name,
                'subproject_dir': builddata.subproject_dir}
      subprojects = []
      for k, v in builddata.subprojects.items():
          c = {'name': k,
               'version': v,
               'descriptive_name': builddata.projects.get(k)}
          subprojects.append(c)
      result['subprojects'] = subprojects
      return result
  
  def list_projinfo_from_source(intr: IntrospectionInterpreter) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]:
      sourcedir = intr.source_root
      files = find_buildsystem_files_list(sourcedir)
      files = [os.path.normpath(x) for x in files]
  
      for i in intr.project_data['subprojects']:
          basedir = os.path.join(intr.subproject_dir, i['name'])
          i['buildsystem_files'] = [x for x in files if x.startswith(basedir)]
          files = [x for x in files if not x.startswith(basedir)]
  
      intr.project_data['buildsystem_files'] = files
      intr.project_data['subproject_dir'] = intr.subproject_dir
      return intr.project_data
  
  def print_results(options, results: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], indent: int) -> int:
      if not results and not options.force_dict:
          print('No command specified')
          return 1
      elif len(results) == 1 and not options.force_dict:
          # Make to keep the existing output format for a single option
          print(json.dumps(results[0][1], indent=indent))
      else:
          out = {}
          for i in results:
              out[i[0]] = i[1]
          print(json.dumps(out, indent=indent))
      return 0
  
  def run(options) -> int:
      datadir = 'meson-private'
      infodir = 'meson-info'
      if options.builddir is not None:
          datadir = os.path.join(options.builddir, datadir)
          infodir = os.path.join(options.builddir, infodir)
      indent = 4 if options.indent else None
      results = []  # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
      sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11]
      intro_types = get_meson_introspection_types(sourcedir=sourcedir)
  
      if 'meson.build' in [os.path.basename(options.builddir), options.builddir]:
          # Make sure that log entries in other parts of meson don't interfere with the JSON output
          mlog.disable()
          backend = backends.get_backend_from_name(options.backend)
          intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()])
          intr.analyze()
          # Re-enable logging just in case
          mlog.enable()
          for key, val in intro_types.items():
              if (not options.all and not getattr(options, key, False)) or not val.no_bd:
                  continue
              results += [(key, val.no_bd(intr))]
          return print_results(options, results, indent)
  
      infofile = get_meson_info_file(infodir)
      if not os.path.isdir(datadir) or not os.path.isdir(infodir) or not os.path.isfile(infofile):
          print('Current directory is not a meson build directory.\n'
                'Please specify a valid build dir or change the working directory to it.\n'
                'It is also possible that the build directory was generated with an old\n'
                'meson version. Please regenerate it in this case.')
          return 1
  
      with open(infofile, 'r') as fp:
          raw = json.load(fp)
          intro_vers = raw.get('introspection', {}).get('version', {}).get('full', '0.0.0')
  
      vers_to_check = get_meson_introspection_required_version()
      for i in vers_to_check:
          if not mesonlib.version_compare(intro_vers, i):
              print('Introspection version {} is not supported. '
                    'The required version is: {}'
                    .format(intro_vers, ' and '.join(vers_to_check)))
              return 1
  
      # Extract introspection information from JSON
      for i in intro_types.keys():
          if not intro_types[i].func:
              continue
          if not options.all and not getattr(options, i, False):
              continue
          curr = os.path.join(infodir, 'intro-{}.json'.format(i))
          if not os.path.isfile(curr):
              print('Introspection file {} does not exist.'.format(curr))
              return 1
          with open(curr, 'r') as fp:
              results += [(i, json.load(fp))]
  
      return print_results(options, results, indent)
  
  updated_introspection_files = []  # type: T.List[str]
  
  def write_intro_info(intro_info: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], info_dir: str) -> None:
      global updated_introspection_files
      for i in intro_info:
          out_file = os.path.join(info_dir, 'intro-{}.json'.format(i[0]))
          tmp_file = os.path.join(info_dir, 'tmp_dump.json')
          with open(tmp_file, 'w') as fp:
              json.dump(i[1], fp)
              fp.flush() # Not sure if this is needed
          os.replace(tmp_file, out_file)
          updated_introspection_files += [i[0]]
  
  def generate_introspection_file(builddata: build.Build, backend: backends.Backend) -> None:
      coredata = builddata.environment.get_coredata()
      intro_types = get_meson_introspection_types(coredata=coredata, builddata=builddata, backend=backend)
      intro_info = []  # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
  
      for key, val in intro_types.items():
          if not val.func:
              continue
          intro_info += [(key, val.func())]
  
      write_intro_info(intro_info, builddata.environment.info_dir)
  
  def update_build_options(coredata: cdata.CoreData, info_dir) -> None:
      intro_info = [
          ('buildoptions', list_buildoptions(coredata))
      ]
  
      write_intro_info(intro_info, info_dir)
  
  def split_version_string(version: str) -> T.Dict[str, T.Union[str, int]]:
      vers_list = version.split('.')
      return {
          'full': version,
          'major': int(vers_list[0] if len(vers_list) > 0 else 0),
          'minor': int(vers_list[1] if len(vers_list) > 1 else 0),
          'patch': int(vers_list[2] if len(vers_list) > 2 else 0)
      }
  
  def write_meson_info_file(builddata: build.Build, errors: list, build_files_updated: bool = False) -> None:
      global updated_introspection_files
      info_dir = builddata.environment.info_dir
      info_file = get_meson_info_file(info_dir)
      intro_types = get_meson_introspection_types()
      intro_info = {}
  
      for i in intro_types.keys():
          if not intro_types[i].func:
              continue
          intro_info[i] = {
              'file': 'intro-{}.json'.format(i),
              'updated': i in updated_introspection_files
          }
  
      info_data = {
          'meson_version': split_version_string(cdata.version),
          'directories': {
              'source': builddata.environment.get_source_dir(),
              'build': builddata.environment.get_build_dir(),
              'info': info_dir,
          },
          'introspection': {
              'version': split_version_string(get_meson_introspection_version()),
              'information': intro_info,
          },
          'build_files_updated': build_files_updated,
      }
  
      if errors:
          info_data['error'] = True
          info_data['error_list'] = [x if isinstance(x, str) else str(x) for x in errors]
      else:
          info_data['error'] = False
  
      # Write the data to disc
      tmp_file = os.path.join(info_dir, 'tmp_dump.json')
      with open(tmp_file, 'w') as fp:
          json.dump(info_data, fp)
          fp.flush()
      os.replace(tmp_file, info_file)
  
files: mesonbuild/mintro.py
files: # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # A tool to run tests in many different ways.
  
  from pathlib import Path
  from collections import namedtuple
  from copy import deepcopy
  import argparse
  import concurrent.futures as conc
  import datetime
  import enum
  import io
  import json
  import multiprocessing
  import os
  import pickle
  import platform
  import random
  import re
  import signal
  import subprocess
  import sys
  import tempfile
  import textwrap
  import time
  import typing as T
  import xml.etree.ElementTree as et
  
  from . import build
  from . import environment
  from . import mlog
  from .dependencies import ExternalProgram
  from .mesonlib import MesonException, get_wine_shortpath, split_args
  from .backend.backends import TestProtocol
  
  if T.TYPE_CHECKING:
      from .backend.backends import TestSerialisation
  
  # GNU autotools interprets a return code of 77 from tests it executes to
  # mean that the test should be skipped.
  GNU_SKIP_RETURNCODE = 77
  
  # GNU autotools interprets a return code of 99 from tests it executes to
  # mean that the test failed even before testing what it is supposed to test.
  GNU_ERROR_RETURNCODE = 99
  
  def is_windows() -> bool:
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_cygwin() -> bool:
      platname = platform.system().lower()
      return 'cygwin' in platname
  
  def determine_worker_count() -> int:
      varname = 'MESON_TESTTHREADS'
      if varname in os.environ:
          try:
              num_workers = int(os.environ[varname])
          except ValueError:
              print('Invalid value in {}, using 1 thread.'.format(varname))
              num_workers = 1
      else:
          try:
              # Fails in some weird environments such as Debian
              # reproducible build.
              num_workers = multiprocessing.cpu_count()
          except Exception:
              num_workers = 1
      return num_workers
  
  def add_arguments(parser: argparse.ArgumentParser) -> None:
      parser.add_argument('--repeat', default=1, dest='repeat', type=int,
                          help='Number of times to run the tests.')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before running tests.')
      parser.add_argument('--gdb', default=False, dest='gdb', action='store_true',
                          help='Run test under gdb.')
      parser.add_argument('--gdb-path', default='gdb', dest='gdb_path',
                          help='Path to the gdb binary (default: gdb).')
      parser.add_argument('--list', default=False, dest='list', action='store_true',
                          help='List available tests.')
      parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args,
                          help='wrapper to run tests with (e.g. Valgrind)')
      parser.add_argument('-C', default='.', dest='wd', type=os.path.abspath,
                          help='directory to cd into before running')
      parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE',
                          help='Only run tests belonging to the given suite.')
      parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE',
                          help='Do not run tests belonging to the given suite.')
      parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false',
                          help='Do not split stderr and stdout in test logs.')
      parser.add_argument('--print-errorlogs', default=False, action='store_true',
                          help="Whether to print failing tests' logs.")
      parser.add_argument('--benchmark', default=False, action='store_true',
                          help="Run benchmarks instead of tests.")
      parser.add_argument('--logbase', default='testlog',
                          help="Base name for log file.")
      parser.add_argument('--num-processes', default=determine_worker_count(), type=int,
                          help='How many parallel processes to use.')
      parser.add_argument('-v', '--verbose', default=False, action='store_true',
                          help='Do not redirect stdout and stderr')
      parser.add_argument('-q', '--quiet', default=False, action='store_true',
                          help='Produce less output to the terminal.')
      parser.add_argument('-t', '--timeout-multiplier', type=float, default=None,
                          help='Define a multiplier for test timeout, for example '
                          ' when running tests in particular conditions they might take'
                          ' more time to execute.')
      parser.add_argument('--setup', default=None, dest='setup',
                          help='Which test setup to use.')
      parser.add_argument('--test-args', default=[], type=split_args,
                          help='Arguments to pass to the specified test(s) or all tests')
      parser.add_argument('args', nargs='*',
                          help='Optional list of tests to run')
  
  
  def returncode_to_status(retcode: int) -> str:
      # Note: We can't use `os.WIFSIGNALED(result.returncode)` and the related
      # functions here because the status returned by subprocess is munged. It
      # returns a negative value if the process was killed by a signal rather than
      # the raw status returned by `wait()`. Also, If a shell sits between Meson
      # the the actual unit test that shell is likely to convert a termination due
      # to a signal into an exit status of 128 plus the signal number.
      if retcode < 0:
          signum = -retcode
          try:
              signame = signal.Signals(signum).name
          except ValueError:
              signame = 'SIGinvalid'
          return '(killed by signal {} {})'.format(signum, signame)
  
      if retcode <= 128:
          return '(exit status {})'.format(retcode)
  
      signum = retcode - 128
      try:
          signame = signal.Signals(signum).name
      except ValueError:
          signame = 'SIGinvalid'
      return '(exit status {} or signal {} {})'.format(retcode, signum, signame)
  
  def env_tuple_to_str(env: T.Iterable[T.Tuple[str, str]]) -> str:
      return ''.join(["{}='{}' ".format(k, v) for k, v in env])
  
  
  class TestException(MesonException):
      pass
  
  
  @enum.unique
  class TestResult(enum.Enum):
  
      OK = 'OK'
      TIMEOUT = 'TIMEOUT'
      SKIP = 'SKIP'
      FAIL = 'FAIL'
      EXPECTEDFAIL = 'EXPECTEDFAIL'
      UNEXPECTEDPASS = 'UNEXPECTEDPASS'
      ERROR = 'ERROR'
  
      @staticmethod
      def maxlen() -> int:
          return 14 # len(UNEXPECTEDPASS)
  
  
  class TAPParser:
      Plan = namedtuple('Plan', ['count', 'late', 'skipped', 'explanation'])
      Bailout = namedtuple('Bailout', ['message'])
      Test = namedtuple('Test', ['number', 'name', 'result', 'explanation'])
      Error = namedtuple('Error', ['message'])
      Version = namedtuple('Version', ['version'])
  
      _MAIN = 1
      _AFTER_TEST = 2
      _YAML = 3
  
      _RE_BAILOUT = re.compile(r'Bail out!\s*(.*)')
      _RE_DIRECTIVE = re.compile(r'(?:\s*\#\s*([Ss][Kk][Ii][Pp]\S*|[Tt][Oo][Dd][Oo])\b\s*(.*))?')
      _RE_PLAN = re.compile(r'1\.\.([0-9]+)' + _RE_DIRECTIVE.pattern)
      _RE_TEST = re.compile(r'((?:not )?ok)\s*(?:([0-9]+)\s*)?([^#]*)' + _RE_DIRECTIVE.pattern)
      _RE_VERSION = re.compile(r'TAP version ([0-9]+)')
      _RE_YAML_START = re.compile(r'(\s+)---.*')
      _RE_YAML_END = re.compile(r'\s+\.\.\.\s*')
  
      def __init__(self, io: T.Iterator[str]):
          self.io = io
  
      def parse_test(self, ok: bool, num: int, name: str, directive: T.Optional[str], explanation: T.Optional[str]) -> \
              T.Generator[T.Union['TAPParser.Test', 'TAPParser.Error'], None, None]:
          name = name.strip()
          explanation = explanation.strip() if explanation else None
          if directive is not None:
              directive = directive.upper()
              if directive == 'SKIP':
                  if ok:
                      yield self.Test(num, name, TestResult.SKIP, explanation)
                      return
              elif directive == 'TODO':
                  yield self.Test(num, name, TestResult.UNEXPECTEDPASS if ok else TestResult.EXPECTEDFAIL, explanation)
                  return
              else:
                  yield self.Error('invalid directive "{}"'.format(directive,))
  
          yield self.Test(num, name, TestResult.OK if ok else TestResult.FAIL, explanation)
  
      def parse(self) -> T.Generator[T.Union['TAPParser.Test', 'TAPParser.Error', 'TAPParser.Version', 'TAPParser.Plan', 'TAPParser.Bailout'], None, None]:
          found_late_test = False
          bailed_out = False
          plan = None
          lineno = 0
          num_tests = 0
          yaml_lineno = None
          yaml_indent = ''
          state = self._MAIN
          version = 12
          while True:
              lineno += 1
              try:
                  line = next(self.io).rstrip()
              except StopIteration:
                  break
  
              # YAML blocks are only accepted after a test
              if state == self._AFTER_TEST:
                  if version >= 13:
                      m = self._RE_YAML_START.match(line)
                      if m:
                          state = self._YAML
                          yaml_lineno = lineno
                          yaml_indent = m.group(1)
                          continue
                  state = self._MAIN
  
              elif state == self._YAML:
                  if self._RE_YAML_END.match(line):
                      state = self._MAIN
                      continue
                  if line.startswith(yaml_indent):
                      continue
                  yield self.Error('YAML block not terminated (started on line {})'.format(yaml_lineno))
                  state = self._MAIN
  
              assert state == self._MAIN
              if line.startswith('#'):
                  continue
  
              m = self._RE_TEST.match(line)
              if m:
                  if plan and plan.late and not found_late_test:
                      yield self.Error('unexpected test after late plan')
                      found_late_test = True
                  num_tests += 1
                  num = num_tests if m.group(2) is None else int(m.group(2))
                  if num != num_tests:
                      yield self.Error('out of order test numbers')
                  yield from self.parse_test(m.group(1) == 'ok', num,
                                             m.group(3), m.group(4), m.group(5))
                  state = self._AFTER_TEST
                  continue
  
              m = self._RE_PLAN.match(line)
              if m:
                  if plan:
                      yield self.Error('more than one plan found')
                  else:
                      count = int(m.group(1))
                      skipped = (count == 0)
                      if m.group(2):
                          if m.group(2).upper().startswith('SKIP'):
                              if count > 0:
                                  yield self.Error('invalid SKIP directive for plan')
                              skipped = True
                          else:
                              yield self.Error('invalid directive for plan')
                      plan = self.Plan(count=count, late=(num_tests > 0),
                                       skipped=skipped, explanation=m.group(3))
                      yield plan
                  continue
  
              m = self._RE_BAILOUT.match(line)
              if m:
                  yield self.Bailout(m.group(1))
                  bailed_out = True
                  continue
  
              m = self._RE_VERSION.match(line)
              if m:
                  # The TAP version is only accepted as the first line
                  if lineno != 1:
                      yield self.Error('version number must be on the first line')
                      continue
                  version = int(m.group(1))
                  if version < 13:
                      yield self.Error('version number should be at least 13')
                  else:
                      yield self.Version(version=version)
                  continue
  
              if len(line) == 0:
                  continue
  
              yield self.Error('unexpected input at line {}'.format((lineno,)))
  
          if state == self._YAML:
              yield self.Error('YAML block not terminated (started on line {})'.format(yaml_lineno))
  
          if not bailed_out and plan and num_tests != plan.count:
              if num_tests < plan.count:
                  yield self.Error('Too few tests run (expected {}, got {})'.format(plan.count, num_tests))
              else:
                  yield self.Error('Too many tests run (expected {}, got {})'.format(plan.count, num_tests))
  
  
  
  class JunitBuilder:
  
      """Builder for Junit test results.
  
      Junit is impossible to stream out, it requires attributes counting the
      total number of tests, failures, skips, and errors in the root element
      and in each test suite. As such, we use a builder class to track each
      test case, and calculate all metadata before writing it out.
  
      For tests with multiple results (like from a TAP test), we record the
      test as a suite with the project_name.test_name. This allows us to track
      each result separately. For tests with only one result (such as exit-code
      tests) we record each one into a suite with the name project_name. The use
      of the project_name allows us to sort subproject tests separately from
      the root project.
      """
  
      def __init__(self, filename: str) -> None:
          self.filename = filename
          self.root = et.Element(
              'testsuites', tests='0', errors='0', failures='0')
          self.suites = {}  # type: T.Dict[str, et.Element]
  
      def log(self, name: str, test: 'TestRun') -> None:
          """Log a single test case."""
          # In this case we have a test binary with multiple results.
          # We want to record this so that each result is recorded
          # separately
          if test.results:
              suitename = '{}.{}'.format(test.project, name)
              assert suitename not in self.suites, 'duplicate suite'
  
              suite = self.suites[suitename] = et.Element(
                  'testsuite',
                  name=suitename,
                  tests=str(len(test.results)),
                  errors=str(sum(1 for r in test.results if r is TestResult.ERROR)),
                  failures=str(sum(1 for r in test.results if r in
                                   {TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})),
                  skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)),
              )
  
              for i, result in enumerate(test.results):
                  # Both name and classname are required. Set them both to the
                  # number of the test in a TAP test, as TAP doesn't give names.
                  testcase = et.SubElement(suite, 'testcase', name=str(i), classname=str(i))
                  if result is TestResult.SKIP:
                      et.SubElement(testcase, 'skipped')
                  elif result is TestResult.ERROR:
                      et.SubElement(testcase, 'error')
                  elif result is TestResult.FAIL:
                      et.SubElement(testcase, 'failure')
                  elif result is TestResult.UNEXPECTEDPASS:
                      fail = et.SubElement(testcase, 'failure')
                      fail.text = 'Test unexpected passed.'
                  elif result is TestResult.TIMEOUT:
                      fail = et.SubElement(testcase, 'failure')
                      fail.text = 'Test did not finish before configured timeout.'
              if test.stdo:
                  out = et.SubElement(suite, 'system-out')
                  out.text = test.stdo.rstrip()
              if test.stde:
                  err = et.SubElement(suite, 'system-err')
                  err.text = test.stde.rstrip()
          else:
              if test.project not in self.suites:
                  suite = self.suites[test.project] = et.Element(
                      'testsuite', name=test.project, tests='1', errors='0',
                      failures='0', skipped='0')
              else:
                  suite = self.suites[test.project]
                  suite.attrib['tests'] = str(int(suite.attrib['tests']) + 1)
  
              testcase = et.SubElement(suite, 'testcase', name=name, classname=name)
              if test.res is TestResult.SKIP:
                  et.SubElement(testcase, 'skipped')
                  suite.attrib['skipped'] = str(int(suite.attrib['skipped']) + 1)
              elif test.res is TestResult.ERROR:
                  et.SubElement(testcase, 'error')
                  suite.attrib['errors'] = str(int(suite.attrib['errors']) + 1)
              elif test.res is TestResult.FAIL:
                  et.SubElement(testcase, 'failure')
                  suite.attrib['failures'] = str(int(suite.attrib['failures']) + 1)
              if test.stdo:
                  out = et.SubElement(testcase, 'system-out')
                  out.text = test.stdo.rstrip()
              if test.stde:
                  err = et.SubElement(testcase, 'system-err')
                  err.text = test.stde.rstrip()
  
      def write(self) -> None:
          """Calculate total test counts and write out the xml result."""
          for suite in self.suites.values():
              self.root.append(suite)
              # Skipped is really not allowed in the "testsuits" element
              for attr in ['tests', 'errors', 'failures']:
                  self.root.attrib[attr] = str(int(self.root.attrib[attr]) + int(suite.attrib[attr]))
  
          tree = et.ElementTree(self.root)
          with open(self.filename, 'wb') as f:
              tree.write(f, encoding='utf-8', xml_declaration=True)
  
  
  class TestRun:
  
      @classmethod
      def make_exitcode(cls, test: 'TestSerialisation', test_env: T.Dict[str, str],
                        returncode: int, starttime: float, duration: float,
                        stdo: T.Optional[str], stde: T.Optional[str],
                        cmd: T.Optional[T.List[str]]) -> 'TestRun':
          if returncode == GNU_SKIP_RETURNCODE:
              res = TestResult.SKIP
          elif returncode == GNU_ERROR_RETURNCODE:
              res = TestResult.ERROR
          elif test.should_fail:
              res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS
          else:
              res = TestResult.FAIL if bool(returncode) else TestResult.OK
          return cls(test, test_env, res, [], returncode, starttime, duration, stdo, stde, cmd)
  
      @classmethod
      def make_tap(cls, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   returncode: int, starttime: float, duration: float,
                   stdo: str, stde: str,
                   cmd: T.Optional[T.List[str]]) -> 'TestRun':
          res = None    # T.Optional[TestResult]
          results = []  # T.List[TestResult]
          failed = False
  
          for i in TAPParser(io.StringIO(stdo)).parse():
              if isinstance(i, TAPParser.Bailout):
                  results.append(TestResult.ERROR)
                  failed = True
              elif isinstance(i, TAPParser.Test):
                  results.append(i.result)
                  if i.result not in {TestResult.OK, TestResult.EXPECTEDFAIL}:
                      failed = True
              elif isinstance(i, TAPParser.Error):
                  results.append(TestResult.ERROR)
                  stde += '\nTAP parsing error: ' + i.message
                  failed = True
  
          if returncode != 0:
              res = TestResult.ERROR
              stde += '\n(test program exited with status code {})'.format(returncode,)
  
          if res is None:
              # Now determine the overall result of the test based on the outcome of the subcases
              if all(t is TestResult.SKIP for t in results):
                  # This includes the case where num_tests is zero
                  res = TestResult.SKIP
              elif test.should_fail:
                  res = TestResult.EXPECTEDFAIL if failed else TestResult.UNEXPECTEDPASS
              else:
                  res = TestResult.FAIL if failed else TestResult.OK
  
          return cls(test, test_env, res, results, returncode, starttime, duration, stdo, stde, cmd)
  
      def __init__(self, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   res: TestResult, results: T.List[TestResult], returncode:
                   int, starttime: float, duration: float,
                   stdo: T.Optional[str], stde: T.Optional[str],
                   cmd: T.Optional[T.List[str]]):
          assert isinstance(res, TestResult)
          self.res = res
          self.results = results  # May be an empty list
          self.returncode = returncode
          self.starttime = starttime
          self.duration = duration
          self.stdo = stdo
          self.stde = stde
          self.cmd = cmd
          self.env = test_env
          self.should_fail = test.should_fail
          self.project = test.project_name
  
      def get_log(self) -> str:
          res = '--- command ---\n'
          if self.cmd is None:
              res += 'NONE\n'
          else:
              test_only_env = set(self.env.items()) - set(os.environ.items())
              starttime_str = time.strftime("%H:%M:%S", time.gmtime(self.starttime))
              res += '{} {}{}\n'.format(
                  starttime_str, env_tuple_to_str(test_only_env), ' '.join(self.cmd)
              )
          if self.stdo:
              res += '--- stdout ---\n'
              res += self.stdo
          if self.stde:
              if res[-1:] != '\n':
                  res += '\n'
              res += '--- stderr ---\n'
              res += self.stde
          if res[-1:] != '\n':
              res += '\n'
          res += '-------\n\n'
          return res
  
  def decode(stream: T.Union[None, bytes]) -> str:
      if stream is None:
          return ''
      try:
          return stream.decode('utf-8')
      except UnicodeDecodeError:
          return stream.decode('iso-8859-1', errors='ignore')
  
  def write_json_log(jsonlogfile: T.TextIO, test_name: str, result: TestRun) -> None:
      jresult = {'name': test_name,
                 'stdout': result.stdo,
                 'result': result.res.value,
                 'starttime': result.starttime,
                 'duration': result.duration,
                 'returncode': result.returncode,
                 'env': result.env,
                 'command': result.cmd}  # type: T.Dict[str, T.Any]
      if result.stde:
          jresult['stderr'] = result.stde
      jsonlogfile.write(json.dumps(jresult) + '\n')
  
  def run_with_mono(fname: str) -> bool:
      return fname.endswith('.exe') and not (is_windows() or is_cygwin())
  
  def load_benchmarks(build_dir: str) -> T.List['TestSerialisation']:
      datafile = Path(build_dir) / 'meson-private' / 'meson_benchmark_setup.dat'
      if not datafile.is_file():
          raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir))
      with datafile.open('rb') as f:
          obj = T.cast(T.List['TestSerialisation'], pickle.load(f))
      return obj
  
  def load_tests(build_dir: str) -> T.List['TestSerialisation']:
      datafile = Path(build_dir) / 'meson-private' / 'meson_test_setup.dat'
      if not datafile.is_file():
          raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir))
      with datafile.open('rb') as f:
          obj = T.cast(T.List['TestSerialisation'], pickle.load(f))
      return obj
  
  
  class SingleTestRunner:
  
      def __init__(self, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   env: T.Dict[str, str], options: argparse.Namespace):
          self.test = test
          self.test_env = test_env
          self.env = env
          self.options = options
  
      def _get_cmd(self) -> T.Optional[T.List[str]]:
          if self.test.fname[0].endswith('.jar'):
              return ['java', '-jar'] + self.test.fname
          elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]):
              return ['mono'] + self.test.fname
          else:
              if self.test.is_cross_built and self.test.needs_exe_wrapper:
                  if self.test.exe_runner is None:
                      # Can not run test on cross compiled executable
                      # because there is no execute wrapper.
                      return None
                  else:
                      if not self.test.exe_runner.found():
                          msg = 'The exe_wrapper defined in the cross file {!r} was not ' \
                                'found. Please check the command and/or add it to PATH.'
                          raise TestException(msg.format(self.test.exe_runner.name))
                      return self.test.exe_runner.get_command() + self.test.fname
              else:
                  return self.test.fname
  
      def run(self) -> TestRun:
          cmd = self._get_cmd()
          if cmd is None:
              skip_stdout = 'Not run because can not execute cross compiled binaries.'
              return TestRun(self.test, self.test_env, TestResult.SKIP, [], GNU_SKIP_RETURNCODE, time.time(), 0.0, skip_stdout, None, None)
          else:
              wrap = TestHarness.get_wrapper(self.options)
              if self.options.gdb:
                  self.test.timeout = None
              return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args)
  
      def _run_cmd(self, cmd: T.List[str]) -> TestRun:
          starttime = time.time()
  
          if len(self.test.extra_paths) > 0:
              self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH']
              winecmd = []
              for c in cmd:
                  winecmd.append(c)
                  if os.path.basename(c).startswith('wine'):
                      self.env['WINEPATH'] = get_wine_shortpath(
                          winecmd,
                          ['Z:' + p for p in self.test.extra_paths] + self.env.get('WINEPATH', '').split(';')
                      )
                      break
  
          # If MALLOC_PERTURB_ is not set, or if it is set to an empty value,
          # (i.e., the test or the environment don't explicitly set it), set
          # it ourselves. We do this unconditionally for regular tests
          # because it is extremely useful to have.
          # Setting MALLOC_PERTURB_="0" will completely disable this feature.
          if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark:
              self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
  
          stdout = None
          stderr = None
          if not self.options.verbose:
              stdout = tempfile.TemporaryFile("wb+")
              stderr = tempfile.TemporaryFile("wb+") if self.options.split else stdout
          if self.test.protocol is TestProtocol.TAP and stderr is stdout:
              stdout = tempfile.TemporaryFile("wb+")
  
          # Let gdb handle ^C instead of us
          if self.options.gdb:
              previous_sigint_handler = signal.getsignal(signal.SIGINT)
              # Make the meson executable ignore SIGINT while gdb is running.
              signal.signal(signal.SIGINT, signal.SIG_IGN)
  
          def preexec_fn() -> None:
              if self.options.gdb:
                  # Restore the SIGINT handler for the child process to
                  # ensure it can handle it.
                  signal.signal(signal.SIGINT, signal.SIG_DFL)
              else:
                  # We don't want setsid() in gdb because gdb needs the
                  # terminal in order to handle ^C and not show tcsetpgrp()
                  # errors avoid not being able to use the terminal.
                  os.setsid()  # type: ignore
  
          p = subprocess.Popen(cmd,
                               stdout=stdout,
                               stderr=stderr,
                               env=self.env,
                               cwd=self.test.workdir,
                               preexec_fn=preexec_fn if not is_windows() else None)
          timed_out = False
          kill_test = False
          if self.test.timeout is None:
              timeout = None
          elif self.options.timeout_multiplier is not None:
              timeout = self.test.timeout * self.options.timeout_multiplier
          else:
              timeout = self.test.timeout
          try:
              p.communicate(timeout=timeout)
          except subprocess.TimeoutExpired:
              if self.options.verbose:
                  print('{} time out (After {} seconds)'.format(self.test.name, timeout))
              timed_out = True
          except KeyboardInterrupt:
              mlog.warning('CTRL-C detected while running {}'.format(self.test.name))
              kill_test = True
          finally:
              if self.options.gdb:
                  # Let us accept ^C again
                  signal.signal(signal.SIGINT, previous_sigint_handler)
  
          additional_error = None
  
          if kill_test or timed_out:
              # Python does not provide multiplatform support for
              # killing a process and all its children so we need
              # to roll our own.
              if is_windows():
                  subprocess.run(['taskkill', '/F', '/T', '/PID', str(p.pid)])
              else:
  
                  def _send_signal_to_process_group(pgid : int, signum : int):
                      """ sends a signal to a process group """
                      try:
                          os.killpg(pgid, signum) # type: ignore
                      except ProcessLookupError:
                          # Sometimes (e.g. with Wine) this happens.
                          # There's nothing we can do (maybe the process
                          # already died) so carry on.
                          pass
  
                  # Send a termination signal to the process group that setsid()
                  # created - giving it a chance to perform any cleanup.
                  _send_signal_to_process_group(p.pid, signal.SIGTERM)
  
                  # Make sure the termination signal actually kills the process
                  # group, otherwise retry with a SIGKILL.
                  try:
                      p.communicate(timeout=0.5)
                  except subprocess.TimeoutExpired:
                      _send_signal_to_process_group(p.pid, signal.SIGKILL)
              try:
                  p.communicate(timeout=1)
              except subprocess.TimeoutExpired:
                  # An earlier kill attempt has not worked for whatever reason.
                  # Try to kill it one last time with a direct call.
                  # If the process has spawned children, they will remain around.
                  p.kill()
                  try:
                      p.communicate(timeout=1)
                  except subprocess.TimeoutExpired:
                      additional_error = 'Test process could not be killed.'
              except ValueError:
                  additional_error = 'Could not read output. Maybe the process has redirected its stdout/stderr?'
          endtime = time.time()
          duration = endtime - starttime
          if additional_error is None:
              if stdout is None:
                  stdo = ''
              else:
                  stdout.seek(0)
                  stdo = decode(stdout.read())
              if stderr is None or stderr is stdout:
                  stde = ''
              else:
                  stderr.seek(0)
                  stde = decode(stderr.read())
          else:
              stdo = ""
              stde = additional_error
          if timed_out:
              return TestRun(self.test, self.test_env, TestResult.TIMEOUT, [], p.returncode, starttime, duration, stdo, stde, cmd)
          else:
              if self.test.protocol is TestProtocol.EXITCODE:
                  return TestRun.make_exitcode(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd)
              else:
                  if self.options.verbose:
                      print(stdo, end='')
                  return TestRun.make_tap(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd)
  
  
  class TestHarness:
      def __init__(self, options: argparse.Namespace):
          self.options = options
          self.collected_logs = []  # type: T.List[str]
          self.fail_count = 0
          self.expectedfail_count = 0
          self.unexpectedpass_count = 0
          self.success_count = 0
          self.skip_count = 0
          self.timeout_count = 0
          self.is_run = False
          self.tests = None
          self.results = []         # type: T.List[TestRun]
          self.logfilename = None   # type: T.Optional[str]
          self.logfile = None       # type: T.Optional[T.TextIO]
          self.jsonlogfile = None   # type: T.Optional[T.TextIO]
          self.junit = None         # type: T.Optional[JunitBuilder]
          if self.options.benchmark:
              self.tests = load_benchmarks(options.wd)
          else:
              self.tests = load_tests(options.wd)
          ss = set()
          for t in self.tests:
              for s in t.suite:
                  ss.add(s)
          self.suites = list(ss)
  
      def __del__(self) -> None:
          self.close_logfiles()
  
      def __enter__(self):
          return self
  
      def __exit__(self, exc_type, exc_value, traceback) -> None:
          self.close_logfiles()
  
      def close_logfiles(self) -> None:
          for f in ['logfile', 'jsonlogfile']:
              lfile =  getattr(self, f)
              if lfile:
                  lfile.close()
                  setattr(self, f, None)
  
      def merge_suite_options(self, options: argparse.Namespace, test: 'TestSerialisation') -> T.Dict[str, str]:
          if ':' in options.setup:
              if options.setup not in self.build_data.test_setups:
                  sys.exit("Unknown test setup '{}'.".format(options.setup))
              current = self.build_data.test_setups[options.setup]
          else:
              full_name = test.project_name + ":" + options.setup
              if full_name not in self.build_data.test_setups:
                  sys.exit("Test setup '{}' not found from project '{}'.".format(options.setup, test.project_name))
              current = self.build_data.test_setups[full_name]
          if not options.gdb:
              options.gdb = current.gdb
          if options.gdb:
              options.verbose = True
          if options.timeout_multiplier is None:
              options.timeout_multiplier = current.timeout_multiplier
      #    if options.env is None:
      #        options.env = current.env # FIXME, should probably merge options here.
          if options.wrapper is not None and current.exe_wrapper is not None:
              sys.exit('Conflict: both test setup and command line specify an exe wrapper.')
          if options.wrapper is None:
              options.wrapper = current.exe_wrapper
          return current.env.get_env(os.environ.copy())
  
      def get_test_runner(self, test: 'TestSerialisation') -> SingleTestRunner:
          options = deepcopy(self.options)
          if not options.setup:
              options.setup = self.build_data.test_setup_default_name
          if options.setup:
              env = self.merge_suite_options(options, test)
          else:
              env = os.environ.copy()
          test_env = test.env.get_env(env)
          env.update(test_env)
          return SingleTestRunner(test, test_env, env, options)
  
      def process_test_result(self, result: TestRun) -> None:
          if result.res is TestResult.TIMEOUT:
              self.timeout_count += 1
          elif result.res is TestResult.SKIP:
              self.skip_count += 1
          elif result.res is TestResult.OK:
              self.success_count += 1
          elif result.res is TestResult.FAIL or result.res is TestResult.ERROR:
              self.fail_count += 1
          elif result.res is TestResult.EXPECTEDFAIL:
              self.expectedfail_count += 1
          elif result.res is TestResult.UNEXPECTEDPASS:
              self.unexpectedpass_count += 1
          else:
              sys.exit('Unknown test result encountered: {}'.format(result.res))
  
      def print_stats(self, test_count: int, name_max_len: int,
                      tests: T.List['TestSerialisation'],
                      name: str, result: TestRun, i: int) -> None:
          ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL)
          bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT,
                          TestResult.UNEXPECTEDPASS, TestResult.ERROR)
          result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res:{reslen}} {dur:.2f}s'.format(
              numlen=len(str(test_count)),
              num=i,
              testcount=test_count,
              name_max_len=name_max_len,
              name=name,
              reslen=TestResult.maxlen(),
              res=result.res.value,
              dur=result.duration)
          if result.res is TestResult.FAIL:
              result_str += ' ' + returncode_to_status(result.returncode)
          if not self.options.quiet or result.res not in ok_statuses:
              if result.res not in ok_statuses and mlog.colorize_console:
                  if result.res in bad_statuses:
                      decorator = mlog.red
                  elif result.res is TestResult.SKIP:
                      decorator = mlog.yellow
                  else:
                      sys.exit('Unreachable code was ... well ... reached.')
                  print(decorator(result_str).get_text(True))
              else:
                  print(result_str)
          result_str += "\n\n" + result.get_log()
          if result.res in bad_statuses:
              if self.options.print_errorlogs:
                  self.collected_logs.append(result_str)
          if self.logfile:
              self.logfile.write(result_str)
          if self.jsonlogfile:
              write_json_log(self.jsonlogfile, name, result)
          if self.junit:
              self.junit.log(name, result)
  
      def print_summary(self) -> None:
          msg = textwrap.dedent('''
              Ok:                 {:<4}
              Expected Fail:      {:<4}
              Fail:               {:<4}
              Unexpected Pass:    {:<4}
              Skipped:            {:<4}
              Timeout:            {:<4}
              ''').format(self.success_count, self.expectedfail_count, self.fail_count,
             self.unexpectedpass_count, self.skip_count, self.timeout_count)
          print(msg)
          if self.logfile:
              self.logfile.write(msg)
          if self.junit:
              self.junit.write()
  
      def print_collected_logs(self) -> None:
          if len(self.collected_logs) > 0:
              if len(self.collected_logs) > 10:
                  print('\nThe output from 10 first failed tests:\n')
              else:
                  print('\nThe output from the failed tests:\n')
              for log in self.collected_logs[:10]:
                  lines = log.splitlines()
                  if len(lines) > 104:
                      print('\n'.join(lines[0:4]))
                      print('--- Listing only the last 100 lines from a long log. ---')
                      lines = lines[-100:]
                  for line in lines:
                      try:
                          print(line)
                      except UnicodeEncodeError:
                          line = line.encode('ascii', errors='replace').decode()
                          print(line)
  
      def total_failure_count(self) -> int:
          return self.fail_count + self.unexpectedpass_count + self.timeout_count
  
      def doit(self) -> int:
          if self.is_run:
              raise RuntimeError('Test harness object can only be used once.')
          self.is_run = True
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.total_failure_count()
  
      @staticmethod
      def split_suite_string(suite: str) -> T.Tuple[str, str]:
          if ':' in suite:
              # mypy can't figure out that str.split(n, 1) will return a list of
              # length 2, so we have to help it.
              return T.cast(T.Tuple[str, str], tuple(suite.split(':', 1)))
          else:
              return suite, ""
  
      @staticmethod
      def test_in_suites(test: 'TestSerialisation', suites: T.List[str]) -> bool:
          for suite in suites:
              (prj_match, st_match) = TestHarness.split_suite_string(suite)
              for prjst in test.suite:
                  (prj, st) = TestHarness.split_suite_string(prjst)
  
                  # the SUITE can be passed as
                  #     suite_name
                  # or
                  #     project_name:suite_name
                  # so we need to select only the test belonging to project_name
  
                  # this if handle the first case (i.e., SUITE == suite_name)
  
                  # in this way we can run tests belonging to different
                  # (sub)projects which share the same suite_name
                  if not st_match and st == prj_match:
                      return True
  
                  # these two conditions are needed to handle the second option
                  # i.e., SUITE == project_name:suite_name
  
                  # in this way we select the only the tests of
                  # project_name with suite_name
                  if prj_match and prj != prj_match:
                      continue
                  if st_match and st != st_match:
                      continue
                  return True
          return False
  
      def test_suitable(self, test: 'TestSerialisation') -> bool:
          return ((not self.options.include_suites or
                  TestHarness.test_in_suites(test, self.options.include_suites)) and not
                  TestHarness.test_in_suites(test, self.options.exclude_suites))
  
      def get_tests(self) -> T.List['TestSerialisation']:
          if not self.tests:
              print('No tests defined.')
              return []
  
          if len(self.options.include_suites) or len(self.options.exclude_suites):
              tests = []
              for tst in self.tests:
                  if self.test_suitable(tst):
                      tests.append(tst)
          else:
              tests = self.tests
  
          # allow specifying test names like "meson test foo1 foo2", where test('foo1', ...)
          if self.options.args:
              tests = [t for t in tests if t.name in self.options.args]
  
          if not tests:
              print('No suitable tests defined.')
              return []
  
          return tests
  
      def open_log_files(self) -> None:
          if not self.options.logbase or self.options.verbose:
              return
  
          namebase = None
          logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
  
          if self.options.wrapper:
              namebase = os.path.basename(self.get_wrapper(self.options)[0])
          elif self.options.setup:
              namebase = self.options.setup.replace(":", "_")
  
          if namebase:
              logfile_base += '-' + namebase.replace(' ', '_')
  
          self.junit = JunitBuilder(logfile_base + '.junit.xml')
  
          self.logfilename = logfile_base + '.txt'
          self.jsonlogfilename = logfile_base + '.json'
  
          self.jsonlogfile = open(self.jsonlogfilename, 'w', encoding='utf-8', errors='replace')
          self.logfile = open(self.logfilename, 'w', encoding='utf-8', errors='surrogateescape')
  
          self.logfile.write('Log of Meson test suite run on {}\n\n'.format(datetime.datetime.now().isoformat()))
          inherit_env = env_tuple_to_str(os.environ.items())
          self.logfile.write('Inherited environment: {}\n\n'.format(inherit_env))
  
      @staticmethod
      def get_wrapper(options: argparse.Namespace) -> T.List[str]:
          wrap = []  # type: T.List[str]
          if options.gdb:
              wrap = [options.gdb_path, '--quiet', '--nh']
              if options.repeat > 1:
                  wrap += ['-ex', 'run', '-ex', 'quit']
              # Signal the end of arguments to gdb
              wrap += ['--args']
          if options.wrapper:
              wrap += options.wrapper
          return wrap
  
      def get_pretty_suite(self, test: 'TestSerialisation') -> str:
          if len(self.suites) > 1 and test.suite:
              rv = TestHarness.split_suite_string(test.suite[0])[0]
              s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite)
              if len(s):
                  rv += ":"
              return rv + s + " / " + test.name
          else:
              return test.name
  
      def run_tests(self, tests: T.List['TestSerialisation']) -> None:
          executor = None
          futures = []  # type: T.List[T.Tuple[conc.Future[TestRun], int, int, T.List[TestSerialisation], str, int]]
          test_count = len(tests)
          name_max_len = max([len(self.get_pretty_suite(test)) for test in tests])
          self.open_log_files()
          startdir = os.getcwd()
          if self.options.wd:
              os.chdir(self.options.wd)
          self.build_data = build.load(os.getcwd())
  
          try:
              for _ in range(self.options.repeat):
                  for i, test in enumerate(tests, 1):
                      visible_name = self.get_pretty_suite(test)
                      single_test = self.get_test_runner(test)
  
                      if not test.is_parallel or self.options.num_processes == 1 or single_test.options.gdb:
                          self.drain_futures(futures)
                          futures = []
                          res = single_test.run()
                          self.process_test_result(res)
                          self.print_stats(test_count, name_max_len, tests, visible_name, res, i)
                      else:
                          if not executor:
                              executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes)
                          f = executor.submit(single_test.run)
                          futures.append((f, test_count, name_max_len, tests, visible_name, i))
                      if self.options.repeat > 1 and self.fail_count:
                          break
                  if self.options.repeat > 1 and self.fail_count:
                      break
  
              self.drain_futures(futures)
              self.print_summary()
              self.print_collected_logs()
  
              if self.logfilename:
                  print('Full log written to {}'.format(self.logfilename))
          finally:
              os.chdir(startdir)
  
      def drain_futures(self, futures: T.List[T.Tuple['conc.Future[TestRun]', int, int, T.List['TestSerialisation'], str, int]]) -> None:
          for x in futures:
              (result, test_count, name_max_len, tests, name, i) = x
              if self.options.repeat > 1 and self.fail_count:
                  result.cancel()
              if self.options.verbose:
                  result.result()
              self.process_test_result(result.result())
              self.print_stats(test_count, name_max_len, tests, name, result.result(), i)
  
      def run_special(self) -> int:
          '''Tests run by the user, usually something like "under gdb 1000 times".'''
          if self.is_run:
              raise RuntimeError('Can not use run_special after a full run.')
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.total_failure_count()
  
  
  def list_tests(th: TestHarness) -> bool:
      tests = th.get_tests()
      for t in tests:
          print(th.get_pretty_suite(t))
      return not tests
  
  def rebuild_all(wd: str) -> bool:
      if not (Path(wd) / 'build.ninja').is_file():
          print('Only ninja backend is supported to rebuild tests before running them.')
          return True
  
      ninja = environment.detect_ninja()
      if not ninja:
          print("Can't find ninja, can't rebuild test.")
          return False
  
      ret = subprocess.run([ninja, '-C', wd]).returncode
      if ret != 0:
          print('Could not rebuild {}'.format(wd))
          return False
  
      return True
  
  def run(options: argparse.Namespace) -> int:
      if options.benchmark:
          options.num_processes = 1
  
      if options.verbose and options.quiet:
          print('Can not be both quiet and verbose at the same time.')
          return 1
  
      check_bin = None
      if options.gdb:
          options.verbose = True
          if options.wrapper:
              print('Must not specify both a wrapper and gdb at the same time.')
              return 1
          check_bin = 'gdb'
  
      if options.wrapper:
          check_bin = options.wrapper[0]
  
      if check_bin is not None:
          exe = ExternalProgram(check_bin, silent=True)
          if not exe.found():
              print('Could not find requested program: {!r}'.format(check_bin))
              return 1
  
      if not options.list and not options.no_rebuild:
          if not rebuild_all(options.wd):
              # We return 125 here in case the build failed.
              # The reason is that exit code 125 tells `git bisect run` that the current commit should be skipped.
              # Thus users can directly use `meson test` to bisect without needing to handle the does-not-build case separately in a wrapper script.
              return 125
  
      with TestHarness(options) as th:
          try:
              if options.list:
                  return list_tests(th)
              if not options.args:
                  return th.doit()
              return th.run_special()
          except TestException as e:
              print('Meson test encountered an error:\n')
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise e
              else:
                  print(e)
              return 1
  
  def run_with_args(args: T.List[str]) -> int:
      parser = argparse.ArgumentParser(prog='meson test')
      add_arguments(parser)
      options = parser.parse_args(args)
      return run(options)
  
files: mesonbuild/mtest.py
files: # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # A tool to run tests in many different ways.
  
  from pathlib import Path
  from collections import namedtuple
  from copy import deepcopy
  import argparse
  import concurrent.futures as conc
  import datetime
  import enum
  import io
  import json
  import multiprocessing
  import os
  import pickle
  import platform
  import random
  import re
  import signal
  import subprocess
  import sys
  import tempfile
  import textwrap
  import time
  import typing as T
  import xml.etree.ElementTree as et
  
  from . import build
  from . import environment
  from . import mlog
  from .dependencies import ExternalProgram
  from .mesonlib import MesonException, get_wine_shortpath, split_args
  
  if T.TYPE_CHECKING:
      from .backend.backends import TestSerialisation
  
  # GNU autotools interprets a return code of 77 from tests it executes to
  # mean that the test should be skipped.
  GNU_SKIP_RETURNCODE = 77
  
  # GNU autotools interprets a return code of 99 from tests it executes to
  # mean that the test failed even before testing what it is supposed to test.
  GNU_ERROR_RETURNCODE = 99
  
  def is_windows() -> bool:
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_cygwin() -> bool:
      platname = platform.system().lower()
      return 'cygwin' in platname
  
  def determine_worker_count() -> int:
      varname = 'MESON_TESTTHREADS'
      if varname in os.environ:
          try:
              num_workers = int(os.environ[varname])
          except ValueError:
              print('Invalid value in {}, using 1 thread.'.format(varname))
              num_workers = 1
      else:
          try:
              # Fails in some weird environments such as Debian
              # reproducible build.
              num_workers = multiprocessing.cpu_count()
          except Exception:
              num_workers = 1
      return num_workers
  
  def add_arguments(parser: argparse.ArgumentParser) -> None:
      parser.add_argument('--repeat', default=1, dest='repeat', type=int,
                          help='Number of times to run the tests.')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before running tests.')
      parser.add_argument('--gdb', default=False, dest='gdb', action='store_true',
                          help='Run test under gdb.')
      parser.add_argument('--gdb-path', default='gdb', dest='gdb_path',
                          help='Path to the gdb binary (default: gdb).')
      parser.add_argument('--list', default=False, dest='list', action='store_true',
                          help='List available tests.')
      parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args,
                          help='wrapper to run tests with (e.g. Valgrind)')
      parser.add_argument('-C', default='.', dest='wd', type=os.path.abspath,
                          help='directory to cd into before running')
      parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE',
                          help='Only run tests belonging to the given suite.')
      parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE',
                          help='Do not run tests belonging to the given suite.')
      parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false',
                          help='Do not split stderr and stdout in test logs.')
      parser.add_argument('--print-errorlogs', default=False, action='store_true',
                          help="Whether to print failing tests' logs.")
      parser.add_argument('--benchmark', default=False, action='store_true',
                          help="Run benchmarks instead of tests.")
      parser.add_argument('--logbase', default='testlog',
                          help="Base name for log file.")
      parser.add_argument('--num-processes', default=determine_worker_count(), type=int,
                          help='How many parallel processes to use.')
      parser.add_argument('-v', '--verbose', default=False, action='store_true',
                          help='Do not redirect stdout and stderr')
      parser.add_argument('-q', '--quiet', default=False, action='store_true',
                          help='Produce less output to the terminal.')
      parser.add_argument('-t', '--timeout-multiplier', type=float, default=None,
                          help='Define a multiplier for test timeout, for example '
                          ' when running tests in particular conditions they might take'
                          ' more time to execute.')
      parser.add_argument('--setup', default=None, dest='setup',
                          help='Which test setup to use.')
      parser.add_argument('--test-args', default=[], type=split_args,
                          help='Arguments to pass to the specified test(s) or all tests')
      parser.add_argument('args', nargs='*',
                          help='Optional list of tests to run')
  
  
  def returncode_to_status(retcode: int) -> str:
      # Note: We can't use `os.WIFSIGNALED(result.returncode)` and the related
      # functions here because the status returned by subprocess is munged. It
      # returns a negative value if the process was killed by a signal rather than
      # the raw status returned by `wait()`. Also, If a shell sits between Meson
      # the the actual unit test that shell is likely to convert a termination due
      # to a signal into an exit status of 128 plus the signal number.
      if retcode < 0:
          signum = -retcode
          try:
              signame = signal.Signals(signum).name
          except ValueError:
              signame = 'SIGinvalid'
          return '(killed by signal {} {})'.format(signum, signame)
  
      if retcode <= 128:
          return '(exit status {})'.format(retcode)
  
      signum = retcode - 128
      try:
          signame = signal.Signals(signum).name
      except ValueError:
          signame = 'SIGinvalid'
      return '(exit status {} or signal {} {})'.format(retcode, signum, signame)
  
  def env_tuple_to_str(env: T.Iterable[T.Tuple[str, str]]) -> str:
      return ''.join(["{}='{}' ".format(k, v) for k, v in env])
  
  
  class TestException(MesonException):
      pass
  
  
  @enum.unique
  class TestResult(enum.Enum):
  
      OK = 'OK'
      TIMEOUT = 'TIMEOUT'
      SKIP = 'SKIP'
      FAIL = 'FAIL'
      EXPECTEDFAIL = 'EXPECTEDFAIL'
      UNEXPECTEDPASS = 'UNEXPECTEDPASS'
      ERROR = 'ERROR'
  
      @staticmethod
      def maxlen() -> int:
          return 14 # len(UNEXPECTEDPASS)
  
  
  class TAPParser:
      Plan = namedtuple('Plan', ['count', 'late', 'skipped', 'explanation'])
      Bailout = namedtuple('Bailout', ['message'])
      Test = namedtuple('Test', ['number', 'name', 'result', 'explanation'])
      Error = namedtuple('Error', ['message'])
      Version = namedtuple('Version', ['version'])
  
      _MAIN = 1
      _AFTER_TEST = 2
      _YAML = 3
  
      _RE_BAILOUT = re.compile(r'Bail out!\s*(.*)')
      _RE_DIRECTIVE = re.compile(r'(?:\s*\#\s*([Ss][Kk][Ii][Pp]\S*|[Tt][Oo][Dd][Oo])\b\s*(.*))?')
      _RE_PLAN = re.compile(r'1\.\.([0-9]+)' + _RE_DIRECTIVE.pattern)
      _RE_TEST = re.compile(r'((?:not )?ok)\s*(?:([0-9]+)\s*)?([^#]*)' + _RE_DIRECTIVE.pattern)
      _RE_VERSION = re.compile(r'TAP version ([0-9]+)')
      _RE_YAML_START = re.compile(r'(\s+)---.*')
      _RE_YAML_END = re.compile(r'\s+\.\.\.\s*')
  
      def __init__(self, io: T.Iterator[str]):
          self.io = io
  
      def parse_test(self, ok: bool, num: int, name: str, directive: T.Optional[str], explanation: T.Optional[str]) -> \
              T.Generator[T.Union['TAPParser.Test', 'TAPParser.Error'], None, None]:
          name = name.strip()
          explanation = explanation.strip() if explanation else None
          if directive is not None:
              directive = directive.upper()
              if directive == 'SKIP':
                  if ok:
                      yield self.Test(num, name, TestResult.SKIP, explanation)
                      return
              elif directive == 'TODO':
                  yield self.Test(num, name, TestResult.UNEXPECTEDPASS if ok else TestResult.EXPECTEDFAIL, explanation)
                  return
              else:
                  yield self.Error('invalid directive "{}"'.format(directive,))
  
          yield self.Test(num, name, TestResult.OK if ok else TestResult.FAIL, explanation)
  
      def parse(self) -> T.Generator[T.Union['TAPParser.Test', 'TAPParser.Error', 'TAPParser.Version', 'TAPParser.Plan', 'TAPParser.Bailout'], None, None]:
          found_late_test = False
          bailed_out = False
          plan = None
          lineno = 0
          num_tests = 0
          yaml_lineno = None
          yaml_indent = ''
          state = self._MAIN
          version = 12
          while True:
              lineno += 1
              try:
                  line = next(self.io).rstrip()
              except StopIteration:
                  break
  
              # YAML blocks are only accepted after a test
              if state == self._AFTER_TEST:
                  if version >= 13:
                      m = self._RE_YAML_START.match(line)
                      if m:
                          state = self._YAML
                          yaml_lineno = lineno
                          yaml_indent = m.group(1)
                          continue
                  state = self._MAIN
  
              elif state == self._YAML:
                  if self._RE_YAML_END.match(line):
                      state = self._MAIN
                      continue
                  if line.startswith(yaml_indent):
                      continue
                  yield self.Error('YAML block not terminated (started on line {})'.format(yaml_lineno))
                  state = self._MAIN
  
              assert state == self._MAIN
              if line.startswith('#'):
                  continue
  
              m = self._RE_TEST.match(line)
              if m:
                  if plan and plan.late and not found_late_test:
                      yield self.Error('unexpected test after late plan')
                      found_late_test = True
                  num_tests += 1
                  num = num_tests if m.group(2) is None else int(m.group(2))
                  if num != num_tests:
                      yield self.Error('out of order test numbers')
                  yield from self.parse_test(m.group(1) == 'ok', num,
                                             m.group(3), m.group(4), m.group(5))
                  state = self._AFTER_TEST
                  continue
  
              m = self._RE_PLAN.match(line)
              if m:
                  if plan:
                      yield self.Error('more than one plan found')
                  else:
                      count = int(m.group(1))
                      skipped = (count == 0)
                      if m.group(2):
                          if m.group(2).upper().startswith('SKIP'):
                              if count > 0:
                                  yield self.Error('invalid SKIP directive for plan')
                              skipped = True
                          else:
                              yield self.Error('invalid directive for plan')
                      plan = self.Plan(count=count, late=(num_tests > 0),
                                       skipped=skipped, explanation=m.group(3))
                      yield plan
                  continue
  
              m = self._RE_BAILOUT.match(line)
              if m:
                  yield self.Bailout(m.group(1))
                  bailed_out = True
                  continue
  
              m = self._RE_VERSION.match(line)
              if m:
                  # The TAP version is only accepted as the first line
                  if lineno != 1:
                      yield self.Error('version number must be on the first line')
                      continue
                  version = int(m.group(1))
                  if version < 13:
                      yield self.Error('version number should be at least 13')
                  else:
                      yield self.Version(version=version)
                  continue
  
              if len(line) == 0:
                  continue
  
              yield self.Error('unexpected input at line {}'.format((lineno,)))
  
          if state == self._YAML:
              yield self.Error('YAML block not terminated (started on line {})'.format(yaml_lineno))
  
          if not bailed_out and plan and num_tests != plan.count:
              if num_tests < plan.count:
                  yield self.Error('Too few tests run (expected {}, got {})'.format(plan.count, num_tests))
              else:
                  yield self.Error('Too many tests run (expected {}, got {})'.format(plan.count, num_tests))
  
  
  
  class JunitBuilder:
  
      """Builder for Junit test results.
  
      Junit is impossible to stream out, it requires attributes counting the
      total number of tests, failures, skips, and errors in the root element
      and in each test suite. As such, we use a builder class to track each
      test case, and calculate all metadata before writing it out.
  
      For tests with multiple results (like from a TAP test), we record the
      test as a suite with the project_name.test_name. This allows us to track
      each result separately. For tests with only one result (such as exit-code
      tests) we record each one into a suite with the name project_name. The use
      of the project_name allows us to sort subproject tests separately from
      the root project.
      """
  
      def __init__(self, filename: str) -> None:
          self.filename = filename
          self.root = et.Element(
              'testsuites', tests='0', errors='0', failures='0')
          self.suites = {}  # type: T.Dict[str, et.Element]
  
      def log(self, name: str, test: 'TestRun') -> None:
          """Log a single test case."""
          # In this case we have a test binary with multiple results.
          # We want to record this so that each result is recorded
          # separately
          if test.results:
              suitename = '{}.{}'.format(test.project, name)
              assert suitename not in self.suites, 'duplicate suite'
  
              suite = self.suites[suitename] = et.Element(
                  'testsuite',
                  name=suitename,
                  tests=str(len(test.results)),
                  errors=str(sum(1 for r in test.results if r is TestResult.ERROR)),
                  failures=str(sum(1 for r in test.results if r in
                                   {TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})),
                  skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)),
              )
  
              for i, result in enumerate(test.results):
                  # Both name and classname are required. Set them both to the
                  # number of the test in a TAP test, as TAP doesn't give names.
                  testcase = et.SubElement(suite, 'testcase', name=str(i), classname=str(i))
                  if result is TestResult.SKIP:
                      et.SubElement(testcase, 'skipped')
                  elif result is TestResult.ERROR:
                      et.SubElement(testcase, 'error')
                  elif result is TestResult.FAIL:
                      et.SubElement(testcase, 'failure')
                  elif result is TestResult.UNEXPECTEDPASS:
                      fail = et.SubElement(testcase, 'failure')
                      fail.text = 'Test unexpected passed.'
                  elif result is TestResult.TIMEOUT:
                      fail = et.SubElement(testcase, 'failure')
                      fail.text = 'Test did not finish before configured timeout.'
              if test.stdo:
                  out = et.SubElement(suite, 'system-out')
                  out.text = test.stdo.rstrip()
              if test.stde:
                  err = et.SubElement(suite, 'system-err')
                  err.text = test.stde.rstrip()
          else:
              if test.project not in self.suites:
                  suite = self.suites[test.project] = et.Element(
                      'testsuite', name=test.project, tests='1', errors='0',
                      failures='0', skipped='0')
              else:
                  suite = self.suites[test.project]
                  suite.attrib['tests'] = str(int(suite.attrib['tests']) + 1)
  
              testcase = et.SubElement(suite, 'testcase', name=name, classname=name)
              if test.res is TestResult.SKIP:
                  et.SubElement(testcase, 'skipped')
                  suite.attrib['skipped'] = str(int(suite.attrib['skipped']) + 1)
              elif test.res is TestResult.ERROR:
                  et.SubElement(testcase, 'error')
                  suite.attrib['errors'] = str(int(suite.attrib['errors']) + 1)
              elif test.res is TestResult.FAIL:
                  et.SubElement(testcase, 'failure')
                  suite.attrib['failures'] = str(int(suite.attrib['failures']) + 1)
              if test.stdo:
                  out = et.SubElement(testcase, 'system-out')
                  out.text = test.stdo.rstrip()
              if test.stde:
                  err = et.SubElement(testcase, 'system-err')
                  err.text = test.stde.rstrip()
  
      def write(self) -> None:
          """Calculate total test counts and write out the xml result."""
          for suite in self.suites.values():
              self.root.append(suite)
              # Skipped is really not allowed in the "testsuits" element
              for attr in ['tests', 'errors', 'failures']:
                  self.root.attrib[attr] = str(int(self.root.attrib[attr]) + int(suite.attrib[attr]))
  
          tree = et.ElementTree(self.root)
          with open(self.filename, 'wb') as f:
              tree.write(f, encoding='utf-8', xml_declaration=True)
  
  
  class TestRun:
  
      @classmethod
      def make_exitcode(cls, test: 'TestSerialisation', test_env: T.Dict[str, str],
                        returncode: int, starttime: float, duration: float,
                        stdo: T.Optional[str], stde: T.Optional[str],
                        cmd: T.Optional[T.List[str]]) -> 'TestRun':
          if returncode == GNU_SKIP_RETURNCODE:
              res = TestResult.SKIP
          elif returncode == GNU_ERROR_RETURNCODE:
              res = TestResult.ERROR
          elif test.should_fail:
              res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS
          else:
              res = TestResult.FAIL if bool(returncode) else TestResult.OK
          return cls(test, test_env, res, [], returncode, starttime, duration, stdo, stde, cmd)
  
      @classmethod
      def make_tap(cls, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   returncode: int, starttime: float, duration: float,
                   stdo: str, stde: str,
                   cmd: T.Optional[T.List[str]]) -> 'TestRun':
          res = None    # T.Optional[TestResult]
          results = []  # T.List[TestResult]
          failed = False
  
          for i in TAPParser(io.StringIO(stdo)).parse():
              if isinstance(i, TAPParser.Bailout):
                  results.append(TestResult.ERROR)
                  failed = True
              elif isinstance(i, TAPParser.Test):
                  results.append(i.result)
                  if i.result not in {TestResult.OK, TestResult.EXPECTEDFAIL}:
                      failed = True
              elif isinstance(i, TAPParser.Error):
                  results.append(TestResult.ERROR)
                  stde += '\nTAP parsing error: ' + i.message
                  failed = True
  
          if returncode != 0:
              res = TestResult.ERROR
              stde += '\n(test program exited with status code {})'.format(returncode,)
  
          if res is None:
              # Now determine the overall result of the test based on the outcome of the subcases
              if all(t is TestResult.SKIP for t in results):
                  # This includes the case where num_tests is zero
                  res = TestResult.SKIP
              elif test.should_fail:
                  res = TestResult.EXPECTEDFAIL if failed else TestResult.UNEXPECTEDPASS
              else:
                  res = TestResult.FAIL if failed else TestResult.OK
  
          return cls(test, test_env, res, results, returncode, starttime, duration, stdo, stde, cmd)
  
      def __init__(self, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   res: TestResult, results: T.List[TestResult], returncode:
                   int, starttime: float, duration: float,
                   stdo: T.Optional[str], stde: T.Optional[str],
                   cmd: T.Optional[T.List[str]]):
          assert isinstance(res, TestResult)
          self.res = res
          self.results = results  # May be an empty list
          self.returncode = returncode
          self.starttime = starttime
          self.duration = duration
          self.stdo = stdo
          self.stde = stde
          self.cmd = cmd
          self.env = test_env
          self.should_fail = test.should_fail
          self.project = test.project_name
  
      def get_log(self) -> str:
          res = '--- command ---\n'
          if self.cmd is None:
              res += 'NONE\n'
          else:
              test_only_env = set(self.env.items()) - set(os.environ.items())
              starttime_str = time.strftime("%H:%M:%S", time.gmtime(self.starttime))
              res += '{} {}{}\n'.format(
                  starttime_str, env_tuple_to_str(test_only_env), ' '.join(self.cmd)
              )
          if self.stdo:
              res += '--- stdout ---\n'
              res += self.stdo
          if self.stde:
              if res[-1:] != '\n':
                  res += '\n'
              res += '--- stderr ---\n'
              res += self.stde
          if res[-1:] != '\n':
              res += '\n'
          res += '-------\n\n'
          return res
  
  def decode(stream: T.Union[None, bytes]) -> str:
      if stream is None:
          return ''
      try:
          return stream.decode('utf-8')
      except UnicodeDecodeError:
          return stream.decode('iso-8859-1', errors='ignore')
  
  def write_json_log(jsonlogfile: T.TextIO, test_name: str, result: TestRun) -> None:
      jresult = {'name': test_name,
                 'stdout': result.stdo,
                 'result': result.res.value,
                 'starttime': result.starttime,
                 'duration': result.duration,
                 'returncode': result.returncode,
                 'env': result.env,
                 'command': result.cmd}  # type: T.Dict[str, T.Any]
      if result.stde:
          jresult['stderr'] = result.stde
      jsonlogfile.write(json.dumps(jresult) + '\n')
  
  def run_with_mono(fname: str) -> bool:
      return fname.endswith('.exe') and not (is_windows() or is_cygwin())
  
  def load_benchmarks(build_dir: str) -> T.List['TestSerialisation']:
      datafile = Path(build_dir) / 'meson-private' / 'meson_benchmark_setup.dat'
      if not datafile.is_file():
          raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir))
      with datafile.open('rb') as f:
          obj = T.cast(T.List['TestSerialisation'], pickle.load(f))
      return obj
  
  def load_tests(build_dir: str) -> T.List['TestSerialisation']:
      datafile = Path(build_dir) / 'meson-private' / 'meson_test_setup.dat'
      if not datafile.is_file():
          raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir))
      with datafile.open('rb') as f:
          obj = T.cast(T.List['TestSerialisation'], pickle.load(f))
      return obj
  
  
  class SingleTestRunner:
  
      def __init__(self, test: 'TestSerialisation', test_env: T.Dict[str, str],
                   env: T.Dict[str, str], options: argparse.Namespace):
          self.test = test
          self.test_env = test_env
          self.env = env
          self.options = options
  
      def _get_cmd(self) -> T.Optional[T.List[str]]:
          if self.test.fname[0].endswith('.jar'):
              return ['java', '-jar'] + self.test.fname
          elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]):
              return ['mono'] + self.test.fname
          else:
              if self.test.is_cross_built and self.test.needs_exe_wrapper:
                  if self.test.exe_runner is None:
                      # Can not run test on cross compiled executable
                      # because there is no execute wrapper.
                      return None
                  else:
                      if not self.test.exe_runner.found():
                          msg = 'The exe_wrapper defined in the cross file {!r} was not ' \
                                'found. Please check the command and/or add it to PATH.'
                          raise TestException(msg.format(self.test.exe_runner.name))
                      return self.test.exe_runner.get_command() + self.test.fname
              else:
                  return self.test.fname
  
      def run(self) -> TestRun:
          cmd = self._get_cmd()
          if cmd is None:
              skip_stdout = 'Not run because can not execute cross compiled binaries.'
              return TestRun(self.test, self.test_env, TestResult.SKIP, [], GNU_SKIP_RETURNCODE, time.time(), 0.0, skip_stdout, None, None)
          else:
              wrap = TestHarness.get_wrapper(self.options)
              if self.options.gdb:
                  self.test.timeout = None
              return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args)
  
      def _run_cmd(self, cmd: T.List[str]) -> TestRun:
          starttime = time.time()
  
          if len(self.test.extra_paths) > 0:
              self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH']
              winecmd = []
              for c in cmd:
                  winecmd.append(c)
                  if os.path.basename(c).startswith('wine'):
                      self.env['WINEPATH'] = get_wine_shortpath(
                          winecmd,
                          ['Z:' + p for p in self.test.extra_paths] + self.env.get('WINEPATH', '').split(';')
                      )
                      break
  
          # If MALLOC_PERTURB_ is not set, or if it is set to an empty value,
          # (i.e., the test or the environment don't explicitly set it), set
          # it ourselves. We do this unconditionally for regular tests
          # because it is extremely useful to have.
          # Setting MALLOC_PERTURB_="0" will completely disable this feature.
          if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark:
              self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
  
          stdout = None
          stderr = None
          if not self.options.verbose:
              stdout = tempfile.TemporaryFile("wb+")
              stderr = tempfile.TemporaryFile("wb+") if self.options.split else stdout
          if self.test.protocol == 'tap' and stderr is stdout:
              stdout = tempfile.TemporaryFile("wb+")
  
          # Let gdb handle ^C instead of us
          if self.options.gdb:
              previous_sigint_handler = signal.getsignal(signal.SIGINT)
              # Make the meson executable ignore SIGINT while gdb is running.
              signal.signal(signal.SIGINT, signal.SIG_IGN)
  
          def preexec_fn() -> None:
              if self.options.gdb:
                  # Restore the SIGINT handler for the child process to
                  # ensure it can handle it.
                  signal.signal(signal.SIGINT, signal.SIG_DFL)
              else:
                  # We don't want setsid() in gdb because gdb needs the
                  # terminal in order to handle ^C and not show tcsetpgrp()
                  # errors avoid not being able to use the terminal.
                  os.setsid()  # type: ignore
  
          p = subprocess.Popen(cmd,
                               stdout=stdout,
                               stderr=stderr,
                               env=self.env,
                               cwd=self.test.workdir,
                               preexec_fn=preexec_fn if not is_windows() else None)
          timed_out = False
          kill_test = False
          if self.test.timeout is None:
              timeout = None
          elif self.options.timeout_multiplier is not None:
              timeout = self.test.timeout * self.options.timeout_multiplier
          else:
              timeout = self.test.timeout
          try:
              p.communicate(timeout=timeout)
          except subprocess.TimeoutExpired:
              if self.options.verbose:
                  print('{} time out (After {} seconds)'.format(self.test.name, timeout))
              timed_out = True
          except KeyboardInterrupt:
              mlog.warning('CTRL-C detected while running {}'.format(self.test.name))
              kill_test = True
          finally:
              if self.options.gdb:
                  # Let us accept ^C again
                  signal.signal(signal.SIGINT, previous_sigint_handler)
  
          additional_error = None
  
          if kill_test or timed_out:
              # Python does not provide multiplatform support for
              # killing a process and all its children so we need
              # to roll our own.
              if is_windows():
                  subprocess.run(['taskkill', '/F', '/T', '/PID', str(p.pid)])
              else:
  
                  def _send_signal_to_process_group(pgid : int, signum : int):
                      """ sends a signal to a process group """
                      try:
                          os.killpg(pgid, signum) # type: ignore
                      except ProcessLookupError:
                          # Sometimes (e.g. with Wine) this happens.
                          # There's nothing we can do (maybe the process
                          # already died) so carry on.
                          pass
  
                  # Send a termination signal to the process group that setsid()
                  # created - giving it a chance to perform any cleanup.
                  _send_signal_to_process_group(p.pid, signal.SIGTERM)
  
                  # Make sure the termination signal actually kills the process
                  # group, otherwise retry with a SIGKILL.
                  try:
                      p.communicate(timeout=0.5)
                  except subprocess.TimeoutExpired:
                      _send_signal_to_process_group(p.pid, signal.SIGKILL)
              try:
                  p.communicate(timeout=1)
              except subprocess.TimeoutExpired:
                  # An earlier kill attempt has not worked for whatever reason.
                  # Try to kill it one last time with a direct call.
                  # If the process has spawned children, they will remain around.
                  p.kill()
                  try:
                      p.communicate(timeout=1)
                  except subprocess.TimeoutExpired:
                      additional_error = 'Test process could not be killed.'
              except ValueError:
                  additional_error = 'Could not read output. Maybe the process has redirected its stdout/stderr?'
          endtime = time.time()
          duration = endtime - starttime
          if additional_error is None:
              if stdout is None:
                  stdo = ''
              else:
                  stdout.seek(0)
                  stdo = decode(stdout.read())
              if stderr is None or stderr is stdout:
                  stde = ''
              else:
                  stderr.seek(0)
                  stde = decode(stderr.read())
          else:
              stdo = ""
              stde = additional_error
          if timed_out:
              return TestRun(self.test, self.test_env, TestResult.TIMEOUT, [], p.returncode, starttime, duration, stdo, stde, cmd)
          else:
              if self.test.protocol == 'exitcode':
                  return TestRun.make_exitcode(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd)
              else:
                  if self.options.verbose:
                      print(stdo, end='')
                  return TestRun.make_tap(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd)
  
  
  class TestHarness:
      def __init__(self, options: argparse.Namespace):
          self.options = options
          self.collected_logs = []  # type: T.List[str]
          self.fail_count = 0
          self.expectedfail_count = 0
          self.unexpectedpass_count = 0
          self.success_count = 0
          self.skip_count = 0
          self.timeout_count = 0
          self.is_run = False
          self.tests = None
          self.results = []         # type: T.List[TestRun]
          self.logfilename = None   # type: T.Optional[str]
          self.logfile = None       # type: T.Optional[T.TextIO]
          self.jsonlogfile = None   # type: T.Optional[T.TextIO]
          self.junit = None         # type: T.Optional[JunitBuilder]
          if self.options.benchmark:
              self.tests = load_benchmarks(options.wd)
          else:
              self.tests = load_tests(options.wd)
          ss = set()
          for t in self.tests:
              for s in t.suite:
                  ss.add(s)
          self.suites = list(ss)
  
      def __del__(self) -> None:
          self.close_logfiles()
  
      def __enter__(self):
          return self
  
      def __exit__(self, exc_type, exc_value, traceback) -> None:
          self.close_logfiles()
  
      def close_logfiles(self) -> None:
          for f in ['logfile', 'jsonlogfile']:
              lfile =  getattr(self, f)
              if lfile:
                  lfile.close()
                  setattr(self, f, None)
  
      def merge_suite_options(self, options: argparse.Namespace, test: 'TestSerialisation') -> T.Dict[str, str]:
          if ':' in options.setup:
              if options.setup not in self.build_data.test_setups:
                  sys.exit("Unknown test setup '{}'.".format(options.setup))
              current = self.build_data.test_setups[options.setup]
          else:
              full_name = test.project_name + ":" + options.setup
              if full_name not in self.build_data.test_setups:
                  sys.exit("Test setup '{}' not found from project '{}'.".format(options.setup, test.project_name))
              current = self.build_data.test_setups[full_name]
          if not options.gdb:
              options.gdb = current.gdb
          if options.gdb:
              options.verbose = True
          if options.timeout_multiplier is None:
              options.timeout_multiplier = current.timeout_multiplier
      #    if options.env is None:
      #        options.env = current.env # FIXME, should probably merge options here.
          if options.wrapper is not None and current.exe_wrapper is not None:
              sys.exit('Conflict: both test setup and command line specify an exe wrapper.')
          if options.wrapper is None:
              options.wrapper = current.exe_wrapper
          return current.env.get_env(os.environ.copy())
  
      def get_test_runner(self, test: 'TestSerialisation') -> SingleTestRunner:
          options = deepcopy(self.options)
          if not options.setup:
              options.setup = self.build_data.test_setup_default_name
          if options.setup:
              env = self.merge_suite_options(options, test)
          else:
              env = os.environ.copy()
          test_env = test.env.get_env(env)
          env.update(test_env)
          return SingleTestRunner(test, test_env, env, options)
  
      def process_test_result(self, result: TestRun) -> None:
          if result.res is TestResult.TIMEOUT:
              self.timeout_count += 1
          elif result.res is TestResult.SKIP:
              self.skip_count += 1
          elif result.res is TestResult.OK:
              self.success_count += 1
          elif result.res is TestResult.FAIL or result.res is TestResult.ERROR:
              self.fail_count += 1
          elif result.res is TestResult.EXPECTEDFAIL:
              self.expectedfail_count += 1
          elif result.res is TestResult.UNEXPECTEDPASS:
              self.unexpectedpass_count += 1
          else:
              sys.exit('Unknown test result encountered: {}'.format(result.res))
  
      def print_stats(self, test_count: int, name_max_len: int,
                      tests: T.List['TestSerialisation'],
                      name: str, result: TestRun, i: int) -> None:
          ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL)
          bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT,
                          TestResult.UNEXPECTEDPASS, TestResult.ERROR)
          result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res:{reslen}} {dur:.2f}s'.format(
              numlen=len(str(test_count)),
              num=i,
              testcount=test_count,
              name_max_len=name_max_len,
              name=name,
              reslen=TestResult.maxlen(),
              res=result.res.value,
              dur=result.duration)
          if result.res is TestResult.FAIL:
              result_str += ' ' + returncode_to_status(result.returncode)
          if not self.options.quiet or result.res not in ok_statuses:
              if result.res not in ok_statuses and mlog.colorize_console:
                  if result.res in bad_statuses:
                      decorator = mlog.red
                  elif result.res is TestResult.SKIP:
                      decorator = mlog.yellow
                  else:
                      sys.exit('Unreachable code was ... well ... reached.')
                  print(decorator(result_str).get_text(True))
              else:
                  print(result_str)
          result_str += "\n\n" + result.get_log()
          if result.res in bad_statuses:
              if self.options.print_errorlogs:
                  self.collected_logs.append(result_str)
          if self.logfile:
              self.logfile.write(result_str)
          if self.jsonlogfile:
              write_json_log(self.jsonlogfile, name, result)
          if self.junit:
              self.junit.log(name, result)
  
      def print_summary(self) -> None:
          msg = textwrap.dedent('''
              Ok:                 {:<4}
              Expected Fail:      {:<4}
              Fail:               {:<4}
              Unexpected Pass:    {:<4}
              Skipped:            {:<4}
              Timeout:            {:<4}
              ''').format(self.success_count, self.expectedfail_count, self.fail_count,
             self.unexpectedpass_count, self.skip_count, self.timeout_count)
          print(msg)
          if self.logfile:
              self.logfile.write(msg)
          if self.junit:
              self.junit.write()
  
      def print_collected_logs(self) -> None:
          if len(self.collected_logs) > 0:
              if len(self.collected_logs) > 10:
                  print('\nThe output from 10 first failed tests:\n')
              else:
                  print('\nThe output from the failed tests:\n')
              for log in self.collected_logs[:10]:
                  lines = log.splitlines()
                  if len(lines) > 104:
                      print('\n'.join(lines[0:4]))
                      print('--- Listing only the last 100 lines from a long log. ---')
                      lines = lines[-100:]
                  for line in lines:
                      try:
                          print(line)
                      except UnicodeEncodeError:
                          line = line.encode('ascii', errors='replace').decode()
                          print(line)
  
      def total_failure_count(self) -> int:
          return self.fail_count + self.unexpectedpass_count + self.timeout_count
  
      def doit(self) -> int:
          if self.is_run:
              raise RuntimeError('Test harness object can only be used once.')
          self.is_run = True
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.total_failure_count()
  
      @staticmethod
      def split_suite_string(suite: str) -> T.Tuple[str, str]:
          if ':' in suite:
              # mypy can't figure out that str.split(n, 1) will return a list of
              # length 2, so we have to help it.
              return T.cast(T.Tuple[str, str], tuple(suite.split(':', 1)))
          else:
              return suite, ""
  
      @staticmethod
      def test_in_suites(test: 'TestSerialisation', suites: T.List[str]) -> bool:
          for suite in suites:
              (prj_match, st_match) = TestHarness.split_suite_string(suite)
              for prjst in test.suite:
                  (prj, st) = TestHarness.split_suite_string(prjst)
  
                  # the SUITE can be passed as
                  #     suite_name
                  # or
                  #     project_name:suite_name
                  # so we need to select only the test belonging to project_name
  
                  # this if handle the first case (i.e., SUITE == suite_name)
  
                  # in this way we can run tests belonging to different
                  # (sub)projects which share the same suite_name
                  if not st_match and st == prj_match:
                      return True
  
                  # these two conditions are needed to handle the second option
                  # i.e., SUITE == project_name:suite_name
  
                  # in this way we select the only the tests of
                  # project_name with suite_name
                  if prj_match and prj != prj_match:
                      continue
                  if st_match and st != st_match:
                      continue
                  return True
          return False
  
      def test_suitable(self, test: 'TestSerialisation') -> bool:
          return ((not self.options.include_suites or
                  TestHarness.test_in_suites(test, self.options.include_suites)) and not
                  TestHarness.test_in_suites(test, self.options.exclude_suites))
  
      def get_tests(self) -> T.List['TestSerialisation']:
          if not self.tests:
              print('No tests defined.')
              return []
  
          if len(self.options.include_suites) or len(self.options.exclude_suites):
              tests = []
              for tst in self.tests:
                  if self.test_suitable(tst):
                      tests.append(tst)
          else:
              tests = self.tests
  
          # allow specifying test names like "meson test foo1 foo2", where test('foo1', ...)
          if self.options.args:
              tests = [t for t in tests if t.name in self.options.args]
  
          if not tests:
              print('No suitable tests defined.')
              return []
  
          return tests
  
      def open_log_files(self) -> None:
          if not self.options.logbase or self.options.verbose:
              return
  
          namebase = None
          logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
  
          if self.options.wrapper:
              namebase = os.path.basename(self.get_wrapper(self.options)[0])
          elif self.options.setup:
              namebase = self.options.setup.replace(":", "_")
  
          if namebase:
              logfile_base += '-' + namebase.replace(' ', '_')
  
          self.junit = JunitBuilder(logfile_base + '.junit.xml')
  
          self.logfilename = logfile_base + '.txt'
          self.jsonlogfilename = logfile_base + '.json'
  
          self.jsonlogfile = open(self.jsonlogfilename, 'w', encoding='utf-8', errors='replace')
          self.logfile = open(self.logfilename, 'w', encoding='utf-8', errors='surrogateescape')
  
          self.logfile.write('Log of Meson test suite run on {}\n\n'.format(datetime.datetime.now().isoformat()))
          inherit_env = env_tuple_to_str(os.environ.items())
          self.logfile.write('Inherited environment: {}\n\n'.format(inherit_env))
  
      @staticmethod
      def get_wrapper(options: argparse.Namespace) -> T.List[str]:
          wrap = []  # type: T.List[str]
          if options.gdb:
              wrap = [options.gdb_path, '--quiet', '--nh']
              if options.repeat > 1:
                  wrap += ['-ex', 'run', '-ex', 'quit']
              # Signal the end of arguments to gdb
              wrap += ['--args']
          if options.wrapper:
              wrap += options.wrapper
          return wrap
  
      def get_pretty_suite(self, test: 'TestSerialisation') -> str:
          if len(self.suites) > 1 and test.suite:
              rv = TestHarness.split_suite_string(test.suite[0])[0]
              s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite)
              if len(s):
                  rv += ":"
              return rv + s + " / " + test.name
          else:
              return test.name
  
      def run_tests(self, tests: T.List['TestSerialisation']) -> None:
          executor = None
          futures = []  # type: T.List[T.Tuple[conc.Future[TestRun], int, int, T.List[TestSerialisation], str, int]]
          test_count = len(tests)
          name_max_len = max([len(self.get_pretty_suite(test)) for test in tests])
          self.open_log_files()
          startdir = os.getcwd()
          if self.options.wd:
              os.chdir(self.options.wd)
          self.build_data = build.load(os.getcwd())
  
          try:
              for _ in range(self.options.repeat):
                  for i, test in enumerate(tests, 1):
                      visible_name = self.get_pretty_suite(test)
                      single_test = self.get_test_runner(test)
  
                      if not test.is_parallel or self.options.num_processes == 1 or single_test.options.gdb:
                          self.drain_futures(futures)
                          futures = []
                          res = single_test.run()
                          self.process_test_result(res)
                          self.print_stats(test_count, name_max_len, tests, visible_name, res, i)
                      else:
                          if not executor:
                              executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes)
                          f = executor.submit(single_test.run)
                          futures.append((f, test_count, name_max_len, tests, visible_name, i))
                      if self.options.repeat > 1 and self.fail_count:
                          break
                  if self.options.repeat > 1 and self.fail_count:
                      break
  
              self.drain_futures(futures)
              self.print_summary()
              self.print_collected_logs()
  
              if self.logfilename:
                  print('Full log written to {}'.format(self.logfilename))
          finally:
              os.chdir(startdir)
  
      def drain_futures(self, futures: T.List[T.Tuple['conc.Future[TestRun]', int, int, T.List['TestSerialisation'], str, int]]) -> None:
          for x in futures:
              (result, test_count, name_max_len, tests, name, i) = x
              if self.options.repeat > 1 and self.fail_count:
                  result.cancel()
              if self.options.verbose:
                  result.result()
              self.process_test_result(result.result())
              self.print_stats(test_count, name_max_len, tests, name, result.result(), i)
  
      def run_special(self) -> int:
          '''Tests run by the user, usually something like "under gdb 1000 times".'''
          if self.is_run:
              raise RuntimeError('Can not use run_special after a full run.')
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.total_failure_count()
  
  
  def list_tests(th: TestHarness) -> bool:
      tests = th.get_tests()
      for t in tests:
          print(th.get_pretty_suite(t))
      return not tests
  
  def rebuild_all(wd: str) -> bool:
      if not (Path(wd) / 'build.ninja').is_file():
          print('Only ninja backend is supported to rebuild tests before running them.')
          return True
  
      ninja = environment.detect_ninja()
      if not ninja:
          print("Can't find ninja, can't rebuild test.")
          return False
  
      ret = subprocess.run([ninja, '-C', wd]).returncode
      if ret != 0:
          print('Could not rebuild {}'.format(wd))
          return False
  
      return True
  
  def run(options: argparse.Namespace) -> int:
      if options.benchmark:
          options.num_processes = 1
  
      if options.verbose and options.quiet:
          print('Can not be both quiet and verbose at the same time.')
          return 1
  
      check_bin = None
      if options.gdb:
          options.verbose = True
          if options.wrapper:
              print('Must not specify both a wrapper and gdb at the same time.')
              return 1
          check_bin = 'gdb'
  
      if options.wrapper:
          check_bin = options.wrapper[0]
  
      if check_bin is not None:
          exe = ExternalProgram(check_bin, silent=True)
          if not exe.found():
              print('Could not find requested program: {!r}'.format(check_bin))
              return 1
  
      if not options.list and not options.no_rebuild:
          if not rebuild_all(options.wd):
              # We return 125 here in case the build failed.
              # The reason is that exit code 125 tells `git bisect run` that the current commit should be skipped.
              # Thus users can directly use `meson test` to bisect without needing to handle the does-not-build case separately in a wrapper script.
              return 125
  
      with TestHarness(options) as th:
          try:
              if options.list:
                  return list_tests(th)
              if not options.args:
                  return th.doit()
              return th.run_special()
          except TestException as e:
              print('Meson test encountered an error:\n')
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise e
              else:
                  print(e)
              return 1
  
  def run_with_args(args: T.List[str]) -> int:
      parser = argparse.ArgumentParser(prog='meson test')
      add_arguments(parser)
      options = parser.parse_args(args)
      return run(options)
  
files: mesonbuild/mtest.py