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.
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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_d
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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.D
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
files: mesonbuild/mtest.py