files.new_contents: # Copyright 2012-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.
  
  import os, pickle, re, shlex, subprocess
  from collections import OrderedDict
  import itertools
  from pathlib import PurePath
  
  from . import backends
  from .. import modules
  from .. import environment, mesonlib
  from .. import build
  from .. import mlog
  from .. import dependencies
  from .. import compilers
  from ..compilers import CompilerArgs
  from ..linkers import ArLinker
  from ..mesonlib import File, MesonException, OrderedSet
  from ..mesonlib import get_compiler_for_source, has_path_sep
  from .backends import CleanTrees, InstallData
  from ..build import InvalidArguments
  
  if mesonlib.is_windows():
      quote_func = lambda s: '"{}"'.format(s)
      execute_wrapper = 'cmd /c'
      rmfile_prefix = 'del /f /s /q {} &&'
  else:
      quote_func = shlex.quote
      execute_wrapper = ''
      rmfile_prefix = 'rm -f {} &&'
  
  def ninja_quote(text, is_build_line=False):
      if is_build_line:
          qcs = ('$', ' ', ':')
      else:
          qcs = ('$', ' ')
      for char in qcs:
          text = text.replace(char, '$' + char)
      if '\n' in text:
          errmsg = '''Ninja does not support newlines in rules. The content was:
  
  %s
  
  Please report this error with a test case to the Meson bug tracker.''' % text
          raise MesonException(errmsg)
      return text
  
  
  class NinjaBuildElement:
      def __init__(self, all_outputs, outfilenames, rule, infilenames):
          if isinstance(outfilenames, str):
              self.outfilenames = [outfilenames]
          else:
              self.outfilenames = outfilenames
          assert(isinstance(rule, str))
          self.rule = rule
          if isinstance(infilenames, str):
              self.infilenames = [infilenames]
          else:
              self.infilenames = infilenames
          self.deps = set()
          self.orderdeps = set()
          self.elems = []
          self.all_outputs = all_outputs
  
      def add_dep(self, dep):
          if isinstance(dep, list):
              self.deps.update(dep)
          else:
              self.deps.add(dep)
  
      def add_orderdep(self, dep):
          if isinstance(dep, list):
              self.orderdeps.update(dep)
          else:
              self.orderdeps.add(dep)
  
      def add_item(self, name, elems):
          if isinstance(elems, str):
              elems = [elems]
          self.elems.append((name, elems))
  
      def write(self, outfile):
          self.check_outputs()
          line = 'build %s: %s %s' % (' '.join([ninja_quote(i, True) for i in self.outfilenames]),
                                      self.rule,
                                      ' '.join([ninja_quote(i, True) for i in self.infilenames]))
          if len(self.deps) > 0:
              line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps])
          if len(self.orderdeps) > 0:
              line += ' || ' + ' '.join([ninja_quote(x, True) for x in self.orderdeps])
          line += '\n'
          # This is the only way I could find to make this work on all
          # platforms including Windows command shell. Slash is a dir separator
          # on Windows, too, so all characters are unambiguous and, more importantly,
          # do not require quoting, unless explicitely specified, which is necessary for
          # the csc compiler.
          line = line.replace('\\', '/')
          outfile.write(line)
  
          # All the entries that should remain unquoted
          raw_names = {'DEPFILE', 'DESC', 'pool', 'description'}
  
          for e in self.elems:
              (name, elems) = e
              should_quote = name not in raw_names
              line = ' %s = ' % name
              newelems = []
              for i in elems:
                  if not should_quote or i == '&&': # Hackety hack hack
                      quoter = ninja_quote
                  else:
                      quoter = lambda x: ninja_quote(quote_func(x))
                  i = i.replace('\\', '\\\\')
                  if quote_func('') == '""':
                      i = i.replace('"', '\\"')
                  newelems.append(quoter(i))
              line += ' '.join(newelems)
              line += '\n'
              outfile.write(line)
          outfile.write('\n')
  
      def check_outputs(self):
          for n in self.outfilenames:
              if n in self.all_outputs:
                  raise MesonException('Multiple producers for Ninja target "%s". Please rename your targets.' % n)
              self.all_outputs[n] = True
  
  class NinjaBackend(backends.Backend):
  
      def __init__(self, build):
          super().__init__(build)
          self.name = 'ninja'
          self.ninja_filename = 'build.ninja'
          self.target_arg_cache = {}
          self.fortran_deps = {}
          self.all_outputs = {}
  
      def create_target_alias(self, to_target, outfile):
          # We need to use aliases for targets that might be used as directory
          # names to workaround a Ninja bug that breaks `ninja -t clean`.
          # This is used for 'reserved' targets such as 'test', 'install',
          # 'benchmark', etc, and also for RunTargets.
          # https://github.com/mesonbuild/meson/issues/1644
          if not to_target.startswith('meson-'):
              m = 'Invalid usage of create_target_alias with {!r}'
              raise AssertionError(m.format(to_target))
          from_target = to_target[len('meson-'):]
          elem = NinjaBuildElement(self.all_outputs, from_target, 'phony', to_target)
          elem.write(outfile)
  
      def detect_vs_dep_prefix(self, tempfilename):
          '''VS writes its dependency in a locale dependent format.
          Detect the search prefix to use.'''
          for compiler in self.build.compilers.values():
              # Have to detect the dependency format
              if compiler.id == 'msvc':
                  break
          else:
              # None of our compilers are MSVC, we're done.
              return open(tempfilename, 'a', encoding='utf-8')
          filename = os.path.join(self.environment.get_scratch_dir(),
                                  'incdetect.c')
          with open(filename, 'w') as f:
              f.write('''#include<stdio.h>
  int dummy;
  ''')
  
          # The output of cl dependency information is language
          # and locale dependent. Any attempt at converting it to
          # Python strings leads to failure. We _must_ do this detection
          # in raw byte mode and write the result in raw bytes.
          pc = subprocess.Popen(['cl', '/showIncludes', '/c', 'incdetect.c'],
                                cwd=self.environment.get_scratch_dir(),
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          (stdo, _) = pc.communicate()
  
          # We want to match 'Note: including file: ' in the line
          # 'Note: including file: d:\MyDir\include\stdio.h', however
          # different locales have different messages with a different
          # number of colons. Match up to the the drive name 'd:\'.
          matchre = re.compile(rb"^(.*\s)[a-zA-Z]:\\.*stdio.h$")
          for line in stdo.split(b'\r\n'):
              match = matchre.match(line)
              if match:
                  with open(tempfilename, 'ab') as binfile:
                      binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n')
                  return open(tempfilename, 'a', encoding='utf-8')
          raise MesonException('Could not determine vs dep dependency prefix string.')
  
      def generate(self, interp):
          self.interpreter = interp
          self.ninja_command = environment.detect_ninja(log=True)
          if self.ninja_command is None:
              raise MesonException('Could not detect Ninja v1.5 or newer')
          outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename)
          tempfilename = outfilename + '~'
          with open(tempfilename, 'w', encoding='utf-8') as outfile:
              outfile.write('# This is the build file for project "%s"\n' %
                            self.build.get_project())
              outfile.write('# It is autogenerated by the Meson build system.\n')
              outfile.write('# Do not edit by hand.\n\n')
              outfile.write('ninja_required_version = 1.5.1\n\n')
          with self.detect_vs_dep_prefix(tempfilename) as outfile:
              self.generate_rules(outfile)
              self.generate_phony(outfile)
              outfile.write('# Build rules for targets\n\n')
              for t in self.build.get_targets().values():
                  self.generate_target(t, outfile)
              outfile.write('# Test rules\n\n')
              self.generate_tests(outfile)
              outfile.write('# Install rules\n\n')
              self.generate_install(outfile)
              self.generate_dist(outfile)
              if 'b_coverage' in self.environment.coredata.base_options and \
                      self.environment.coredata.base_options['b_coverage'].value:
                  outfile.write('# Coverage rules\n\n')
                  self.generate_coverage_rules(outfile)
              outfile.write('# Suffix\n\n')
              self.generate_utils(outfile)
              self.generate_ending(outfile)
          # Only overwrite the old build file after the new one has been
          # fully created.
          os.replace(tempfilename, outfilename)
          self.generate_compdb()
  
      # http://clang.llvm.org/docs/JSONCompilationDatabase.html
      def generate_compdb(self):
          pch_compilers = ['%s_PCH' % i for i in self.build.compilers]
          native_compilers = ['%s_COMPILER' % i for i in self.build.compilers]
          cross_compilers = ['%s_CROSS_COMPILER' % i for i in self.build.cross_compilers]
          ninja_compdb = [self.ninja_command, '-t', 'compdb'] + pch_compilers + native_compilers + cross_compilers
          builddir = self.environment.get_build_dir()
          try:
              jsondb = subprocess.check_output(ninja_compdb, cwd=builddir)
              with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f:
                  f.write(jsondb)
          except Exception:
              mlog.warning('Could not create compilation database.')
  
      # Get all generated headers. Any source file might need them so
      # we need to add an order dependency to them.
      def get_generated_headers(self, target):
          if hasattr(target, 'cached_generated_headers'):
              return target.cached_generated_headers
          header_deps = []
          # XXX: Why don't we add deps to CustomTarget headers here?
          for genlist in target.get_generated_sources():
              if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
                  continue
              for src in genlist.get_outputs():
                  if self.environment.is_header(src):
                      header_deps.append(self.get_target_generated_dir(target, genlist, src))
          if 'vala' in target.compilers and not isinstance(target, build.Executable):
              vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header)
              header_deps.append(vala_header)
          # Recurse and find generated headers
          for dep in itertools.chain(target.link_targets, target.link_whole_targets):
              if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
                  header_deps += self.get_generated_headers(dep)
          target.cached_generated_headers = header_deps
          return header_deps
  
      def get_target_generated_sources(self, target):
          """
          Returns a dictionary with the keys being the path to the file
          (relative to the build directory) of that type and the value
          being the GeneratorList or CustomTarget that generated it.
          """
          srcs = OrderedDict()
          for gensrc in target.get_generated_sources():
              for s in gensrc.get_outputs():
                  f = self.get_target_generated_dir(target, gensrc, s)
                  srcs[f] = s
          return srcs
  
      def get_target_sources(self, target):
          srcs = OrderedDict()
          for s in target.get_sources():
              # BuildTarget sources are always mesonlib.File files which are
              # either in the source root, or generated with configure_file and
              # in the build root
              if not isinstance(s, File):
                  raise InvalidArguments('All sources in target {!r} must be of type mesonlib.File'.format(s))
              f = s.rel_to_builddir(self.build_to_src)
              srcs[f] = s
          return srcs
  
      # Languages that can mix with C or C++ but don't support unity builds yet
      # because the syntax we use for unity builds is specific to C/++/ObjC/++.
      # Assembly files cannot be unitified and neither can LLVM IR files
      langs_cant_unity = ('d', 'fortran')
  
      def get_target_source_can_unity(self, target, source):
          if isinstance(source, File):
              source = source.fname
          if self.environment.is_llvm_ir(source) or \
             self.environment.is_assembly(source):
              return False
          suffix = os.path.splitext(source)[1][1:]
          for lang in self.langs_cant_unity:
              if lang not in target.compilers:
                  continue
              if suffix in target.compilers[lang].file_suffixes:
                  return False
          return True
  
      def generate_target(self, target, outfile):
          if isinstance(target, build.CustomTarget):
              self.generate_custom_target(target, outfile)
          if isinstance(target, build.RunTarget):
              self.generate_run_target(target, outfile)
          name = target.get_id()
          if name in self.processed_targets:
              return
          self.processed_targets[name] = True
          # Generate rules for all dependency targets
          self.process_target_dependencies(target, outfile)
          # If target uses a language that cannot link to C objects,
          # just generate for that language and return.
          if isinstance(target, build.Jar):
              self.generate_jar_target(target, outfile)
              return
          if 'rust' in target.compilers:
              self.generate_rust_target(target, outfile)
              return
          if 'cs' in target.compilers:
              self.generate_cs_target(target, outfile)
              return
          if 'swift' in target.compilers:
              self.generate_swift_target(target, outfile)
              return
  
          # Now we handle the following languages:
          # ObjC++, ObjC, C++, C, D, Fortran, Vala
  
          # target_sources:
          #   Pre-existing target C/C++ sources to be built; dict of full path to
          #   source relative to build root and the original File object.
          # generated_sources:
          #   GeneratedList and CustomTarget sources to be built; dict of the full
          #   path to source relative to build root and the generating target/list
          # vala_generated_sources:
          #   Array of sources generated by valac that have to be compiled
          if 'vala' in target.compilers:
              # Sources consumed by valac are filtered out. These only contain
              # C/C++ sources, objects, generated libs, and unknown sources now.
              target_sources, generated_sources, \
                  vala_generated_sources = self.generate_vala_compile(target, outfile)
          else:
              target_sources = self.get_target_sources(target)
              generated_sources = self.get_target_generated_sources(target)
              vala_generated_sources = []
          self.scan_fortran_module_outputs(target)
          # Generate rules for GeneratedLists
          self.generate_generator_list_rules(target, outfile)
  
          # Generate rules for building the remaining source files in this target
          outname = self.get_target_filename(target)
          obj_list = []
          use_pch = self.environment.coredata.base_options.get('b_pch', False)
          is_unity = self.is_unity(target)
          if use_pch and target.has_pch():
              pch_objects = self.generate_pch(target, outfile)
          else:
              pch_objects = []
          header_deps = []
          unity_src = []
          unity_deps = [] # Generated sources that must be built before compiling a Unity target.
          header_deps += self.get_generated_headers(target)
  
          if is_unity:
              # Warn about incompatible sources if a unity build is enabled
              langs = set(target.compilers.keys())
              langs_cant = langs.intersection(self.langs_cant_unity)
              if langs_cant:
                  langs_are = langs = ', '.join(langs_cant).upper()
                  langs_are += ' are' if len(langs_cant) > 1 else ' is'
                  msg = '{} not supported in Unity builds yet, so {} ' \
                        'sources in the {!r} target will be compiled normally' \
                        ''.format(langs_are, langs, target.name)
                  mlog.log(mlog.red('FIXME'), msg)
  
          # Get a list of all generated headers that will be needed while building
          # this target's sources (generated sources and pre-existing sources).
          # This will be set as dependencies of all the target's sources. At the
          # same time, also deal with generated sources that need to be compiled.
          generated_source_files = []
          for rel_src, gensrc in generated_sources.items():
              dirpart, fnamepart = os.path.split(rel_src)
              raw_src = File(True, dirpart, fnamepart)
              if self.environment.is_source(rel_src) and not self.environment.is_header(rel_src):
                  if is_unity and self.get_target_source_can_unity(target, rel_src):
                      unity_deps.append(raw_src)
                      abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
                      unity_src.append(abs_src)
                  else:
                      generated_source_files.append(raw_src)
              elif self.environment.is_object(rel_src):
                  obj_list.append(rel_src)
              elif self.environment.is_library(rel_src):
                  pass
              else:
                  # Assume anything not specifically a source file is a header. This is because
                  # people generate files with weird suffixes (.inc, .fh) that they then include
                  # in their source files.
                  header_deps.append(raw_src)
          # These are the generated source files that need to be built for use by
          # this target. We create the Ninja build file elements for this here
          # because we need `header_deps` to be fully generated in the above loop.
          for src in generated_source_files:
              if self.environment.is_llvm_ir(src):
                  o = self.generate_llvm_ir_compile(target, outfile, src)
              else:
                  o = self.generate_single_compile(target, outfile, src, True,
                                                   header_deps=header_deps)
              obj_list.append(o)
  
          # Generate compilation targets for C sources generated from Vala
          # sources. This can be extended to other $LANG->C compilers later if
          # necessary. This needs to be separate for at least Vala
          vala_generated_source_files = []
          for src in vala_generated_sources:
              dirpart, fnamepart = os.path.split(src)
              raw_src = File(True, dirpart, fnamepart)
              if is_unity:
                  unity_src.append(os.path.join(self.environment.get_build_dir(), src))
                  header_deps.append(raw_src)
              else:
                  # Generated targets are ordered deps because the must exist
                  # before the sources compiling them are used. After the first
                  # compile we get precise dependency info from dep files.
                  # This should work in all cases. If it does not, then just
                  # move them from orderdeps to proper deps.
                  if self.environment.is_header(src):
                      header_deps.append(raw_src)
                  else:
                      # We gather all these and generate compile rules below
                      # after `header_deps` (above) is fully generated
                      vala_generated_source_files.append(raw_src)
          for src in vala_generated_source_files:
              # Passing 'vala' here signifies that we want the compile
              # arguments to be specialized for C code generated by
              # valac. For instance, no warnings should be emitted.
              obj_list.append(self.generate_single_compile(target, outfile, src, 'vala', [], header_deps))
  
          # Generate compile targets for all the pre-existing sources for this target
          for f, src in target_sources.items():
              if not self.environment.is_header(src):
                  if self.environment.is_llvm_ir(src):
                      obj_list.append(self.generate_llvm_ir_compile(target, outfile, src))
                  elif is_unity and self.get_target_source_can_unity(target, src):
                      abs_src = os.path.join(self.environment.get_build_dir(),
                                             src.rel_to_builddir(self.build_to_src))
                      unity_src.append(abs_src)
                  else:
                      obj_list.append(self.generate_single_compile(target, outfile, src, False, [], header_deps))
          obj_list += self.flatten_object_list(target)
          if is_unity:
              for src in self.generate_unity_files(target, unity_src):
                  obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps))
          linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
          elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects, stdlib_args=stdlib_args)
          self.generate_shlib_aliases(target, self.get_target_dir(target))
          elem.write(outfile)
  
      def process_target_dependencies(self, target, outfile):
          for t in target.get_dependencies():
              if t.get_id() not in self.processed_targets:
                  self.generate_target(t, outfile)
  
      def custom_target_generator_inputs(self, target, outfile):
          for s in target.sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, build.GeneratedList):
                  self.generate_genlist_for_target(s, target, outfile)
  
      def unwrap_dep_list(self, target):
          deps = []
          for i in target.get_dependencies():
              # FIXME, should not grab element at zero but rather expand all.
              if isinstance(i, list):
                  i = i[0]
              # Add a dependency on all the outputs of this target
              for output in i.get_outputs():
                  deps.append(os.path.join(self.get_target_dir(i), output))
          return deps
  
      def generate_custom_target(self, target, outfile):
          self.custom_target_generator_inputs(target, outfile)
          (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
          deps = self.unwrap_dep_list(target)
          deps += self.get_custom_target_depend_files(target)
          desc = 'Generating {0} with a {1} command.'
          if target.build_always:
              deps.append('PHONY')
          if target.depfile is None:
              rulename = 'CUSTOM_COMMAND'
          else:
              rulename = 'CUSTOM_COMMAND_DEP'
          elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs)
          elem.add_dep(deps)
          for d in target.extra_depends:
              # Add a dependency on all the outputs of this target
              for output in d.get_outputs():
                  elem.add_dep(os.path.join(self.get_target_dir(d), output))
          serialize = False
          extra_paths = []
          # If the target requires capturing stdout, then use the serialized
          # executable wrapper to capture that output and save it to a file.
          if target.capture:
              serialize = True
          # If the command line requires a newline, also use the wrapper, as
          # ninja does not support them in its build rule syntax.
          if any('\n' in c for c in cmd):
              serialize = True
          # Windows doesn't have -rpath, so for EXEs that need DLLs built within
          # the project, we need to set PATH so the DLLs are found. We use
          # a serialized executable wrapper for that and check if the
          # CustomTarget command needs extra paths first.
          if mesonlib.is_windows() or mesonlib.is_cygwin():
              extra_bdeps = target.get_transitive_build_target_deps()
              extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps)
              if extra_paths:
                  serialize = True
          if serialize:
              exe_data = self.serialize_executable(target.command[0], cmd[1:],
                                                   # All targets are built from the build dir
                                                   self.environment.get_build_dir(),
                                                   extra_paths=extra_paths,
                                                   capture=ofilenames[0] if target.capture else None)
              cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data]
              cmd_type = 'meson_exe.py custom'
          else:
              cmd_type = 'custom'
          if target.depfile is not None:
              rel_dfile = os.path.join(self.get_target_dir(target), target.depfile)
              abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
              os.makedirs(abs_pdir, exist_ok=True)
              elem.add_item('DEPFILE', rel_dfile)
          cmd = self.replace_paths(target, cmd)
          elem.add_item('COMMAND', cmd)
          elem.add_item('description', desc.format(target.name, cmd_type))
          elem.write(outfile)
          self.processed_targets[target.get_id()] = True
  
      def generate_run_target(self, target, outfile):
          cmd = self.environment.get_build_command() + ['--internal', 'commandrunner']
          deps = self.unwrap_dep_list(target)
          arg_strings = []
          for i in target.args:
              if isinstance(i, str):
                  arg_strings.append(i)
              elif isinstance(i, (build.BuildTarget, build.CustomTarget)):
                  relfname = self.get_target_filename(i)
                  arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
                  deps.append(relfname)
              elif isinstance(i, mesonlib.File):
                  relfname = i.rel_to_builddir(self.build_to_src)
                  arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
              else:
                  raise AssertionError('Unreachable code in generate_run_target: ' + str(i))
          if target.subproject != '':
              subproject_prefix = '{}@@'.format(target.subproject)
          else:
              subproject_prefix = ''
          target_name = 'meson-{}{}'.format(subproject_prefix, target.name)
          elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
          cmd += [self.environment.get_source_dir(),
                  self.environment.get_build_dir(),
                  target.subdir] + self.environment.get_build_command()
          texe = target.command
          try:
              texe = texe.held_object
          except AttributeError:
              pass
          if isinstance(texe, build.Executable):
              abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))
              deps.append(self.get_target_filename(texe))
              if self.environment.is_cross_build() and \
                 self.environment.cross_info.need_exe_wrapper():
                  exe_wrap = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
                  if exe_wrap is not None:
                      cmd += [exe_wrap]
              cmd.append(abs_exe)
          elif isinstance(texe, dependencies.ExternalProgram):
              cmd += texe.get_command()
          elif isinstance(texe, build.CustomTarget):
              deps.append(self.get_target_filename(texe))
              cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))]
          elif isinstance(texe, mesonlib.File):
              cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()))
          else:
              cmd.append(target.command)
          cmd += arg_strings
  
          elem.add_dep(deps)
          cmd = self.replace_paths(target, cmd)
          elem.add_item('COMMAND', cmd)
          elem.add_item('description', 'Running external command %s.' % target.name)
          elem.add_item('pool', 'console')
          elem.write(outfile)
          # Alias that runs the target defined above with the name the user specified
          self.create_target_alias(target_name, outfile)
          self.processed_targets[target.get_id()] = True
  
      def generate_coverage_command(self, elem, outputs):
          elem.add_item('COMMAND', self.environment.get_build_command() +
                        ['--internal', 'coverage'] +
                        outputs +
                        [self.environment.get_source_dir(),
                         os.path.join(self.environment.get_source_dir(),
                                      self.build.get_subproject_dir()),
                         self.environment.get_build_dir(),
                         self.environment.get_log_dir()])
  
      def generate_coverage_rules(self, outfile):
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, [])
          e.add_item('description', 'Generates coverage reports.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage', outfile)
          self.generate_coverage_legacy_rules(outfile)
  
      def generate_coverage_legacy_rules(self, outfile):
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--xml'])
          e.add_item('description', 'Generates XML coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-xml', outfile)
  
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--text'])
          e.add_item('description', 'Generates text coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-text', outfile)
  
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--html'])
          e.add_item('description', 'Generates HTML coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-html', outfile)
  
      def generate_install(self, outfile):
          install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
          if self.environment.is_cross_build():
              bins = self.environment.cross_info.config['binaries']
              if 'strip' not in bins:
                  mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
                  strip_bin = None
              else:
                  strip_bin = mesonlib.stringlistify(bins['strip'])
          else:
              strip_bin = self.environment.native_strip_bin
          d = InstallData(self.environment.get_source_dir(),
                          self.environment.get_build_dir(),
                          self.environment.get_prefix(),
                          strip_bin,
                          self.environment.coredata.get_builtin_option('install_umask'),
                          self.environment.get_build_command() + ['introspect'])
          elem = NinjaBuildElement(self.all_outputs, 'meson-install', 'CUSTOM_COMMAND', 'PHONY')
          elem.add_dep('all')
          elem.add_item('DESC', 'Installing files.')
          elem.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'install', install_data_file])
          elem.add_item('pool', 'console')
          self.generate_depmf_install(d)
          self.generate_target_install(d)
          self.generate_header_install(d)
          self.generate_man_install(d)
          self.generate_data_install(d)
          self.generate_custom_install_script(d)
          self.generate_subdir_install(d)
          elem.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-install', outfile)
  
          with open(install_data_file, 'wb') as ofile:
              pickle.dump(d, ofile)
  
      def generate_target_install(self, d):
          for t in self.build.get_targets().values():
              if not t.should_install():
                  continue
              # Find the installation directory.
              outdirs = t.get_custom_install_dir()
              custom_install_dir = False
              if outdirs[0] is not None and outdirs[0] is not True:
                  # Either the value is set, or is set to False which means
                  # we want this specific output out of many outputs to not
                  # be installed.
                  custom_install_dir = True
              elif isinstance(t, build.SharedModule):
                  outdirs[0] = self.environment.get_shared_module_dir()
              elif isinstance(t, build.SharedLibrary):
                  outdirs[0] = self.environment.get_shared_lib_dir()
              elif isinstance(t, build.StaticLibrary):
                  outdirs[0] = self.environment.get_static_lib_dir()
              elif isinstance(t, build.Executable):
                  outdirs[0] = self.environment.get_bindir()
              else:
                  assert(isinstance(t, build.BuildTarget))
                  # XXX: Add BuildTarget-specific install dir cases here
                  outdirs[0] = self.environment.get_libdir()
              # Sanity-check the outputs and install_dirs
              num_outdirs, num_out = len(outdirs), len(t.get_outputs())
              if num_outdirs != 1 and num_outdirs != num_out:
                  m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
                      "Pass 'false' for outputs that should not be installed and 'true' for\n
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/backend/ninjabackend.py
files.old_contents: # Copyright 2012-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.
  
  import os, pickle, re, shlex, subprocess
  from collections import OrderedDict
  import itertools
  from pathlib import PurePath
  
  from . import backends
  from .. import modules
  from .. import environment, mesonlib
  from .. import build
  from .. import mlog
  from .. import dependencies
  from .. import compilers
  from ..compilers import CompilerArgs
  from ..linkers import ArLinker
  from ..mesonlib import File, MesonException, OrderedSet
  from ..mesonlib import get_compiler_for_source, has_path_sep
  from .backends import CleanTrees, InstallData
  from ..build import InvalidArguments
  
  if mesonlib.is_windows():
      quote_func = lambda s: '"{}"'.format(s)
      execute_wrapper = 'cmd /c'
      rmfile_prefix = 'del /f /s /q {} &&'
  else:
      quote_func = shlex.quote
      execute_wrapper = ''
      rmfile_prefix = 'rm -f {} &&'
  
  def ninja_quote(text, is_build_line=False):
      if is_build_line:
          qcs = ('$', ' ', ':')
      else:
          qcs = ('$', ' ')
      for char in qcs:
          text = text.replace(char, '$' + char)
      if '\n' in text:
          errmsg = '''Ninja does not support newlines in rules. The content was:
  
  %s
  
  Please report this error with a test case to the Meson bug tracker.''' % text
          raise MesonException(errmsg)
      return text
  
  
  class NinjaBuildElement:
      def __init__(self, all_outputs, outfilenames, rule, infilenames):
          if isinstance(outfilenames, str):
              self.outfilenames = [outfilenames]
          else:
              self.outfilenames = outfilenames
          assert(isinstance(rule, str))
          self.rule = rule
          if isinstance(infilenames, str):
              self.infilenames = [infilenames]
          else:
              self.infilenames = infilenames
          self.deps = set()
          self.orderdeps = set()
          self.elems = []
          self.all_outputs = all_outputs
  
      def add_dep(self, dep):
          if isinstance(dep, list):
              self.deps.update(dep)
          else:
              self.deps.add(dep)
  
      def add_orderdep(self, dep):
          if isinstance(dep, list):
              self.orderdeps.update(dep)
          else:
              self.orderdeps.add(dep)
  
      def add_item(self, name, elems):
          if isinstance(elems, str):
              elems = [elems]
          self.elems.append((name, elems))
  
      def write(self, outfile):
          self.check_outputs()
          line = 'build %s: %s %s' % (' '.join([ninja_quote(i, True) for i in self.outfilenames]),
                                      self.rule,
                                      ' '.join([ninja_quote(i, True) for i in self.infilenames]))
          if len(self.deps) > 0:
              line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps])
          if len(self.orderdeps) > 0:
              line += ' || ' + ' '.join([ninja_quote(x, True) for x in self.orderdeps])
          line += '\n'
          # This is the only way I could find to make this work on all
          # platforms including Windows command shell. Slash is a dir separator
          # on Windows, too, so all characters are unambiguous and, more importantly,
          # do not require quoting, unless explicitely specified, which is necessary for
          # the csc compiler.
          line = line.replace('\\', '/')
          outfile.write(line)
  
          # All the entries that should remain unquoted
          raw_names = {'DEPFILE', 'DESC', 'pool', 'description'}
  
          for e in self.elems:
              (name, elems) = e
              should_quote = name not in raw_names
              line = ' %s = ' % name
              newelems = []
              for i in elems:
                  if not should_quote or i == '&&': # Hackety hack hack
                      quoter = ninja_quote
                  else:
                      quoter = lambda x: ninja_quote(quote_func(x))
                  i = i.replace('\\', '\\\\')
                  if quote_func('') == '""':
                      i = i.replace('"', '\\"')
                  newelems.append(quoter(i))
              line += ' '.join(newelems)
              line += '\n'
              outfile.write(line)
          outfile.write('\n')
  
      def check_outputs(self):
          for n in self.outfilenames:
              if n in self.all_outputs:
                  raise MesonException('Multiple producers for Ninja target "%s". Please rename your targets.' % n)
              self.all_outputs[n] = True
  
  class NinjaBackend(backends.Backend):
  
      def __init__(self, build):
          super().__init__(build)
          self.name = 'ninja'
          self.ninja_filename = 'build.ninja'
          self.target_arg_cache = {}
          self.fortran_deps = {}
          self.all_outputs = {}
  
      def create_target_alias(self, to_target, outfile):
          # We need to use aliases for targets that might be used as directory
          # names to workaround a Ninja bug that breaks `ninja -t clean`.
          # This is used for 'reserved' targets such as 'test', 'install',
          # 'benchmark', etc, and also for RunTargets.
          # https://github.com/mesonbuild/meson/issues/1644
          if not to_target.startswith('meson-'):
              m = 'Invalid usage of create_target_alias with {!r}'
              raise AssertionError(m.format(to_target))
          from_target = to_target[len('meson-'):]
          elem = NinjaBuildElement(self.all_outputs, from_target, 'phony', to_target)
          elem.write(outfile)
  
      def detect_vs_dep_prefix(self, tempfilename):
          '''VS writes its dependency in a locale dependent format.
          Detect the search prefix to use.'''
          for compiler in self.build.compilers.values():
              # Have to detect the dependency format
              if compiler.id == 'msvc':
                  break
          else:
              # None of our compilers are MSVC, we're done.
              return open(tempfilename, 'a', encoding='utf-8')
          filename = os.path.join(self.environment.get_scratch_dir(),
                                  'incdetect.c')
          with open(filename, 'w') as f:
              f.write('''#include<stdio.h>
  int dummy;
  ''')
  
          # The output of cl dependency information is language
          # and locale dependent. Any attempt at converting it to
          # Python strings leads to failure. We _must_ do this detection
          # in raw byte mode and write the result in raw bytes.
          pc = subprocess.Popen(['cl', '/showIncludes', '/c', 'incdetect.c'],
                                cwd=self.environment.get_scratch_dir(),
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          (stdo, _) = pc.communicate()
  
          # We want to match 'Note: including file: ' in the line
          # 'Note: including file: d:\MyDir\include\stdio.h', however
          # different locales have different messages with a different
          # number of colons. Match up to the the drive name 'd:\'.
          matchre = re.compile(rb"^(.*\s)[a-zA-Z]:\\.*stdio.h$")
          for line in stdo.split(b'\r\n'):
              match = matchre.match(line)
              if match:
                  with open(tempfilename, 'ab') as binfile:
                      binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n')
                  return open(tempfilename, 'a', encoding='utf-8')
          raise MesonException('Could not determine vs dep dependency prefix string.')
  
      def generate(self, interp):
          self.interpreter = interp
          self.ninja_command = environment.detect_ninja(log=True)
          if self.ninja_command is None:
              raise MesonException('Could not detect Ninja v1.5 or newer')
          outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename)
          tempfilename = outfilename + '~'
          with open(tempfilename, 'w', encoding='utf-8') as outfile:
              outfile.write('# This is the build file for project "%s"\n' %
                            self.build.get_project())
              outfile.write('# It is autogenerated by the Meson build system.\n')
              outfile.write('# Do not edit by hand.\n\n')
              outfile.write('ninja_required_version = 1.5.1\n\n')
          with self.detect_vs_dep_prefix(tempfilename) as outfile:
              self.generate_rules(outfile)
              self.generate_phony(outfile)
              outfile.write('# Build rules for targets\n\n')
              for t in self.build.get_targets().values():
                  self.generate_target(t, outfile)
              outfile.write('# Test rules\n\n')
              self.generate_tests(outfile)
              outfile.write('# Install rules\n\n')
              self.generate_install(outfile)
              self.generate_dist(outfile)
              if 'b_coverage' in self.environment.coredata.base_options and \
                      self.environment.coredata.base_options['b_coverage'].value:
                  outfile.write('# Coverage rules\n\n')
                  self.generate_coverage_rules(outfile)
              outfile.write('# Suffix\n\n')
              self.generate_utils(outfile)
              self.generate_ending(outfile)
          # Only overwrite the old build file after the new one has been
          # fully created.
          os.replace(tempfilename, outfilename)
          self.generate_compdb()
  
      # http://clang.llvm.org/docs/JSONCompilationDatabase.html
      def generate_compdb(self):
          pch_compilers = ['%s_PCH' % i for i in self.build.compilers]
          native_compilers = ['%s_COMPILER' % i for i in self.build.compilers]
          cross_compilers = ['%s_CROSS_COMPILER' % i for i in self.build.cross_compilers]
          ninja_compdb = [self.ninja_command, '-t', 'compdb'] + pch_compilers + native_compilers + cross_compilers
          builddir = self.environment.get_build_dir()
          try:
              jsondb = subprocess.check_output(ninja_compdb, cwd=builddir)
              with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f:
                  f.write(jsondb)
          except Exception:
              mlog.warning('Could not create compilation database.')
  
      # Get all generated headers. Any source file might need them so
      # we need to add an order dependency to them.
      def get_generated_headers(self, target):
          if hasattr(target, 'cached_generated_headers'):
              return target.cached_generated_headers
          header_deps = []
          # XXX: Why don't we add deps to CustomTarget headers here?
          for genlist in target.get_generated_sources():
              if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
                  continue
              for src in genlist.get_outputs():
                  if self.environment.is_header(src):
                      header_deps.append(self.get_target_generated_dir(target, genlist, src))
          if 'vala' in target.compilers and not isinstance(target, build.Executable):
              vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header)
              header_deps.append(vala_header)
          # Recurse and find generated headers
          for dep in itertools.chain(target.link_targets, target.link_whole_targets):
              if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
                  header_deps += self.get_generated_headers(dep)
          target.cached_generated_headers = header_deps
          return header_deps
  
      def get_target_generated_sources(self, target):
          """
          Returns a dictionary with the keys being the path to the file
          (relative to the build directory) of that type and the value
          being the GeneratorList or CustomTarget that generated it.
          """
          srcs = OrderedDict()
          for gensrc in target.get_generated_sources():
              for s in gensrc.get_outputs():
                  f = self.get_target_generated_dir(target, gensrc, s)
                  srcs[f] = s
          return srcs
  
      def get_target_sources(self, target):
          srcs = OrderedDict()
          for s in target.get_sources():
              # BuildTarget sources are always mesonlib.File files which are
              # either in the source root, or generated with configure_file and
              # in the build root
              if not isinstance(s, File):
                  raise InvalidArguments('All sources in target {!r} must be of type mesonlib.File'.format(s))
              f = s.rel_to_builddir(self.build_to_src)
              srcs[f] = s
          return srcs
  
      # Languages that can mix with C or C++ but don't support unity builds yet
      # because the syntax we use for unity builds is specific to C/++/ObjC/++.
      # Assembly files cannot be unitified and neither can LLVM IR files
      langs_cant_unity = ('d', 'fortran')
  
      def get_target_source_can_unity(self, target, source):
          if isinstance(source, File):
              source = source.fname
          if self.environment.is_llvm_ir(source) or \
             self.environment.is_assembly(source):
              return False
          suffix = os.path.splitext(source)[1][1:]
          for lang in self.langs_cant_unity:
              if lang not in target.compilers:
                  continue
              if suffix in target.compilers[lang].file_suffixes:
                  return False
          return True
  
      def generate_target(self, target, outfile):
          if isinstance(target, build.CustomTarget):
              self.generate_custom_target(target, outfile)
          if isinstance(target, build.RunTarget):
              self.generate_run_target(target, outfile)
          name = target.get_id()
          if name in self.processed_targets:
              return
          self.processed_targets[name] = True
          # Generate rules for all dependency targets
          self.process_target_dependencies(target, outfile)
          # If target uses a language that cannot link to C objects,
          # just generate for that language and return.
          if isinstance(target, build.Jar):
              self.generate_jar_target(target, outfile)
              return
          if 'rust' in target.compilers:
              self.generate_rust_target(target, outfile)
              return
          if 'cs' in target.compilers:
              self.generate_cs_target(target, outfile)
              return
          if 'swift' in target.compilers:
              self.generate_swift_target(target, outfile)
              return
  
          # Now we handle the following languages:
          # ObjC++, ObjC, C++, C, D, Fortran, Vala
  
          # target_sources:
          #   Pre-existing target C/C++ sources to be built; dict of full path to
          #   source relative to build root and the original File object.
          # generated_sources:
          #   GeneratedList and CustomTarget sources to be built; dict of the full
          #   path to source relative to build root and the generating target/list
          # vala_generated_sources:
          #   Array of sources generated by valac that have to be compiled
          if 'vala' in target.compilers:
              # Sources consumed by valac are filtered out. These only contain
              # C/C++ sources, objects, generated libs, and unknown sources now.
              target_sources, generated_sources, \
                  vala_generated_sources = self.generate_vala_compile(target, outfile)
          else:
              target_sources = self.get_target_sources(target)
              generated_sources = self.get_target_generated_sources(target)
              vala_generated_sources = []
          self.scan_fortran_module_outputs(target)
          # Generate rules for GeneratedLists
          self.generate_generator_list_rules(target, outfile)
  
          # Generate rules for building the remaining source files in this target
          outname = self.get_target_filename(target)
          obj_list = []
          use_pch = self.environment.coredata.base_options.get('b_pch', False)
          is_unity = self.is_unity(target)
          if use_pch and target.has_pch():
              pch_objects = self.generate_pch(target, outfile)
          else:
              pch_objects = []
          header_deps = []
          unity_src = []
          unity_deps = [] # Generated sources that must be built before compiling a Unity target.
          header_deps += self.get_generated_headers(target)
  
          if is_unity:
              # Warn about incompatible sources if a unity build is enabled
              langs = set(target.compilers.keys())
              langs_cant = langs.intersection(self.langs_cant_unity)
              if langs_cant:
                  langs_are = langs = ', '.join(langs_cant).upper()
                  langs_are += ' are' if len(langs_cant) > 1 else ' is'
                  msg = '{} not supported in Unity builds yet, so {} ' \
                        'sources in the {!r} target will be compiled normally' \
                        ''.format(langs_are, langs, target.name)
                  mlog.log(mlog.red('FIXME'), msg)
  
          # Get a list of all generated headers that will be needed while building
          # this target's sources (generated sources and pre-existing sources).
          # This will be set as dependencies of all the target's sources. At the
          # same time, also deal with generated sources that need to be compiled.
          generated_source_files = []
          for rel_src, gensrc in generated_sources.items():
              dirpart, fnamepart = os.path.split(rel_src)
              raw_src = File(True, dirpart, fnamepart)
              if self.environment.is_source(rel_src) and not self.environment.is_header(rel_src):
                  if is_unity and self.get_target_source_can_unity(target, rel_src):
                      unity_deps.append(raw_src)
                      abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
                      unity_src.append(abs_src)
                  else:
                      generated_source_files.append(raw_src)
              elif self.environment.is_object(rel_src):
                  obj_list.append(rel_src)
              elif self.environment.is_library(rel_src):
                  pass
              else:
                  # Assume anything not specifically a source file is a header. This is because
                  # people generate files with weird suffixes (.inc, .fh) that they then include
                  # in their source files.
                  header_deps.append(raw_src)
          # These are the generated source files that need to be built for use by
          # this target. We create the Ninja build file elements for this here
          # because we need `header_deps` to be fully generated in the above loop.
          for src in generated_source_files:
              if self.environment.is_llvm_ir(src):
                  o = self.generate_llvm_ir_compile(target, outfile, src)
              else:
                  o = self.generate_single_compile(target, outfile, src, True,
                                                   header_deps=header_deps)
              obj_list.append(o)
  
          # Generate compilation targets for C sources generated from Vala
          # sources. This can be extended to other $LANG->C compilers later if
          # necessary. This needs to be separate for at least Vala
          vala_generated_source_files = []
          for src in vala_generated_sources:
              dirpart, fnamepart = os.path.split(src)
              raw_src = File(True, dirpart, fnamepart)
              if is_unity:
                  unity_src.append(os.path.join(self.environment.get_build_dir(), src))
                  header_deps.append(raw_src)
              else:
                  # Generated targets are ordered deps because the must exist
                  # before the sources compiling them are used. After the first
                  # compile we get precise dependency info from dep files.
                  # This should work in all cases. If it does not, then just
                  # move them from orderdeps to proper deps.
                  if self.environment.is_header(src):
                      header_deps.append(raw_src)
                  else:
                      # We gather all these and generate compile rules below
                      # after `header_deps` (above) is fully generated
                      vala_generated_source_files.append(raw_src)
          for src in vala_generated_source_files:
              # Passing 'vala' here signifies that we want the compile
              # arguments to be specialized for C code generated by
              # valac. For instance, no warnings should be emitted.
              obj_list.append(self.generate_single_compile(target, outfile, src, 'vala', [], header_deps))
  
          # Generate compile targets for all the pre-existing sources for this target
          for f, src in target_sources.items():
              if not self.environment.is_header(src):
                  if self.environment.is_llvm_ir(src):
                      obj_list.append(self.generate_llvm_ir_compile(target, outfile, src))
                  elif is_unity and self.get_target_source_can_unity(target, src):
                      abs_src = os.path.join(self.environment.get_build_dir(),
                                             src.rel_to_builddir(self.build_to_src))
                      unity_src.append(abs_src)
                  else:
                      obj_list.append(self.generate_single_compile(target, outfile, src, False, [], header_deps))
          obj_list += self.flatten_object_list(target)
          if is_unity:
              for src in self.generate_unity_files(target, unity_src):
                  obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps))
          linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
          elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects, stdlib_args=stdlib_args)
          self.generate_shlib_aliases(target, self.get_target_dir(target))
          elem.write(outfile)
  
      def process_target_dependencies(self, target, outfile):
          for t in target.get_dependencies():
              if t.get_id() not in self.processed_targets:
                  self.generate_target(t, outfile)
  
      def custom_target_generator_inputs(self, target, outfile):
          for s in target.sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, build.GeneratedList):
                  self.generate_genlist_for_target(s, target, outfile)
  
      def unwrap_dep_list(self, target):
          deps = []
          for i in target.get_dependencies():
              # FIXME, should not grab element at zero but rather expand all.
              if isinstance(i, list):
                  i = i[0]
              # Add a dependency on all the outputs of this target
              for output in i.get_outputs():
                  deps.append(os.path.join(self.get_target_dir(i), output))
          return deps
  
      def generate_custom_target(self, target, outfile):
          self.custom_target_generator_inputs(target, outfile)
          (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
          deps = self.unwrap_dep_list(target)
          deps += self.get_custom_target_depend_files(target)
          desc = 'Generating {0} with a {1} command.'
          if target.build_always:
              deps.append('PHONY')
          if target.depfile is None:
              rulename = 'CUSTOM_COMMAND'
          else:
              rulename = 'CUSTOM_COMMAND_DEP'
          elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs)
          elem.add_dep(deps)
          for d in target.extra_depends:
              # Add a dependency on all the outputs of this target
              for output in d.get_outputs():
                  elem.add_dep(os.path.join(self.get_target_dir(d), output))
          serialize = False
          extra_paths = []
          # If the target requires capturing stdout, then use the serialized
          # executable wrapper to capture that output and save it to a file.
          if target.capture:
              serialize = True
          # If the command line requires a newline, also use the wrapper, as
          # ninja does not support them in its build rule syntax.
          if any('\n' in c for c in cmd):
              serialize = True
          # Windows doesn't have -rpath, so for EXEs that need DLLs built within
          # the project, we need to set PATH so the DLLs are found. We use
          # a serialized executable wrapper for that and check if the
          # CustomTarget command needs extra paths first.
          if mesonlib.is_windows() or mesonlib.is_cygwin():
              extra_bdeps = target.get_transitive_build_target_deps()
              extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps)
              if extra_paths:
                  serialize = True
          if serialize:
              exe_data = self.serialize_executable(target.command[0], cmd[1:],
                                                   # All targets are built from the build dir
                                                   self.environment.get_build_dir(),
                                                   extra_paths=extra_paths,
                                                   capture=ofilenames[0] if target.capture else None)
              cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data]
              cmd_type = 'meson_exe.py custom'
          else:
              cmd_type = 'custom'
          if target.depfile is not None:
              rel_dfile = os.path.join(self.get_target_dir(target), target.depfile)
              abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
              os.makedirs(abs_pdir, exist_ok=True)
              elem.add_item('DEPFILE', rel_dfile)
          cmd = self.replace_paths(target, cmd)
          elem.add_item('COMMAND', cmd)
          elem.add_item('description', desc.format(target.name, cmd_type))
          elem.write(outfile)
          self.processed_targets[target.get_id()] = True
  
      def generate_run_target(self, target, outfile):
          cmd = self.environment.get_build_command() + ['--internal', 'commandrunner']
          deps = self.unwrap_dep_list(target)
          arg_strings = []
          for i in target.args:
              if isinstance(i, str):
                  arg_strings.append(i)
              elif isinstance(i, (build.BuildTarget, build.CustomTarget)):
                  relfname = self.get_target_filename(i)
                  arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
                  deps.append(relfname)
              elif isinstance(i, mesonlib.File):
                  relfname = i.rel_to_builddir(self.build_to_src)
                  arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
              else:
                  raise AssertionError('Unreachable code in generate_run_target: ' + str(i))
          if target.subproject != '':
              subproject_prefix = '{}@@'.format(target.subproject)
          else:
              subproject_prefix = ''
          target_name = 'meson-{}{}'.format(subproject_prefix, target.name)
          elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
          cmd += [self.environment.get_source_dir(),
                  self.environment.get_build_dir(),
                  target.subdir] + self.environment.get_build_command()
          texe = target.command
          try:
              texe = texe.held_object
          except AttributeError:
              pass
          if isinstance(texe, build.Executable):
              abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))
              deps.append(self.get_target_filename(texe))
              if self.environment.is_cross_build() and \
                 self.environment.cross_info.need_exe_wrapper():
                  exe_wrap = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
                  if exe_wrap is not None:
                      cmd += [exe_wrap]
              cmd.append(abs_exe)
          elif isinstance(texe, dependencies.ExternalProgram):
              cmd += texe.get_command()
          elif isinstance(texe, build.CustomTarget):
              deps.append(self.get_target_filename(texe))
              cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))]
          elif isinstance(texe, mesonlib.File):
              cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()))
          else:
              cmd.append(target.command)
          cmd += arg_strings
  
          elem.add_dep(deps)
          cmd = self.replace_paths(target, cmd)
          elem.add_item('COMMAND', cmd)
          elem.add_item('description', 'Running external command %s.' % target.name)
          elem.add_item('pool', 'console')
          elem.write(outfile)
          # Alias that runs the target defined above with the name the user specified
          self.create_target_alias(target_name, outfile)
          self.processed_targets[target.get_id()] = True
  
      def generate_coverage_command(self, elem, outputs):
          elem.add_item('COMMAND', self.environment.get_build_command() +
                        ['--internal', 'coverage'] +
                        outputs +
                        [self.environment.get_source_dir(),
                         os.path.join(self.environment.get_source_dir(),
                                      self.build.get_subproject_dir()),
                         self.environment.get_build_dir(),
                         self.environment.get_log_dir()])
  
      def generate_coverage_rules(self, outfile):
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, [])
          e.add_item('description', 'Generates coverage reports.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage', outfile)
          self.generate_coverage_legacy_rules(outfile)
  
      def generate_coverage_legacy_rules(self, outfile):
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--xml'])
          e.add_item('description', 'Generates XML coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-xml', outfile)
  
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--text'])
          e.add_item('description', 'Generates text coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-text', outfile)
  
          e = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'CUSTOM_COMMAND', 'PHONY')
          self.generate_coverage_command(e, ['--html'])
          e.add_item('description', 'Generates HTML coverage report.')
          e.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-coverage-html', outfile)
  
      def generate_install(self, outfile):
          install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
          if self.environment.is_cross_build():
              bins = self.environment.cross_info.config['binaries']
              if 'strip' not in bins:
                  mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
                  strip_bin = None
              else:
                  strip_bin = mesonlib.stringlistify(bins['strip'])
          else:
              strip_bin = self.environment.native_strip_bin
          d = InstallData(self.environment.get_source_dir(),
                          self.environment.get_build_dir(),
                          self.environment.get_prefix(),
                          strip_bin,
                          self.environment.coredata.get_builtin_option('install_umask'),
                          self.environment.get_build_command() + ['introspect'])
          elem = NinjaBuildElement(self.all_outputs, 'meson-install', 'CUSTOM_COMMAND', 'PHONY')
          elem.add_dep('all')
          elem.add_item('DESC', 'Installing files.')
          elem.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'install', install_data_file])
          elem.add_item('pool', 'console')
          self.generate_depmf_install(d)
          self.generate_target_install(d)
          self.generate_header_install(d)
          self.generate_man_install(d)
          self.generate_data_install(d)
          self.generate_custom_install_script(d)
          self.generate_subdir_install(d)
          elem.write(outfile)
          # Alias that runs the target defined above
          self.create_target_alias('meson-install', outfile)
  
          with open(install_data_file, 'wb') as ofile:
              pickle.dump(d, ofile)
  
      def generate_target_install(self, d):
          for t in self.build.get_targets().values():
              if not t.should_install():
                  continue
              # Find the installation directory.
              outdirs = t.get_custom_install_dir()
              custom_install_dir = False
              if outdirs[0] is not None and outdirs[0] is not True:
                  # Either the value is set, or is set to False which means
                  # we want this specific output out of many outputs to not
                  # be installed.
                  custom_install_dir = True
              elif isinstance(t, build.SharedModule):
                  outdirs[0] = self.environment.get_shared_module_dir()
              elif isinstance(t, build.SharedLibrary):
                  outdirs[0] = self.environment.get_shared_lib_dir()
              elif isinstance(t, build.StaticLibrary):
                  outdirs[0] = self.environment.get_static_lib_dir()
              elif isinstance(t, build.Executable):
                  outdirs[0] = self.environment.get_bindir()
              else:
                  assert(isinstance(t, build.BuildTarget))
                  # XXX: Add BuildTarget-specific install dir cases here
                  outdirs[0] = self.environment.get_libdir()
              # Sanity-check the outputs and install_dirs
              num_outdirs, num_out = len(outdirs), len(t.get_outputs())
              if num_outdirs != 1 and num_outdirs != num_out:
                  m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
                      "Pass 'false' for outputs that should not be installed and 'true' for\n
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/backend/ninjabackend.py
files.new_contents: # Copyright 2012-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.
  
  import copy, os, re
  from collections import OrderedDict
  import itertools, pathlib
  import pickle
  
  from . import environment
  from . import dependencies
  from . import mlog
  from .mesonlib import File, MesonException, listify, extract_as_list
  from .mesonlib import typeslistify, stringlistify, classify_unity_sources
  from .mesonlib import get_filenames_templates_dict, substitute_values
  from .mesonlib import for_windows, for_darwin, for_cygwin, for_android, has_path_sep
  from .compilers import is_object, clike_langs, sort_clike, lang_suffixes
  from .interpreterbase import FeatureNew, FeatureNewKwargs
  
  pch_kwargs = set(['c_pch', 'cpp_pch'])
  
  lang_arg_kwargs = set([
      'c_args',
      'cpp_args',
      'd_args',
      'd_import_dirs',
      'd_unittest',
      'd_module_versions',
      'fortran_args',
      'java_args',
      'objc_args',
      'objcpp_args',
      'rust_args',
      'vala_args',
      'cs_args',
  ])
  
  vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi'])
  rust_kwargs = set(['rust_crate_type'])
  cs_kwargs = set(['resources', 'cs_args'])
  
  buildtarget_kwargs = set([
      'build_by_default',
      'build_rpath',
      'dependencies',
      'extra_files',
      'gui_app',
      'link_with',
      'link_whole',
      'link_args',
      'link_depends',
      'implicit_include_directories',
      'include_directories',
      'install',
      'install_rpath',
      'install_dir',
      'install_mode',
      'name_prefix',
      'name_suffix',
      'native',
      'objects',
      'override_options',
      'sources',
  ])
  
  known_build_target_kwargs = (
      buildtarget_kwargs |
      lang_arg_kwargs |
      pch_kwargs |
      vala_kwargs |
      rust_kwargs |
      cs_kwargs)
  
  known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic'}
  known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs'}
  known_shmod_kwargs = known_build_target_kwargs
  known_stlib_kwargs = known_build_target_kwargs | {'pic'}
  known_jar_kwargs = known_exe_kwargs | {'main_class'}
  
  class InvalidArguments(MesonException):
      pass
  
  class Build:
      """A class that holds the status of one build including
      all dependencies and so on.
      """
  
      def __init__(self, environment):
          self.project_name = 'name of master project'
          self.project_version = None
          self.environment = environment
          self.projects = {}
          self.targets = OrderedDict()
          self.compilers = OrderedDict()
          self.cross_compilers = OrderedDict()
          self.global_args = {}
          self.projects_args = {}
          self.global_link_args = {}
          self.projects_link_args = {}
          self.tests = []
          self.benchmarks = []
          self.headers = []
          self.man = []
          self.data = []
          self.static_linker = None
          self.static_cross_linker = None
          self.subprojects = {}
          self.subproject_dir = ''
          self.install_scripts = []
          self.postconf_scripts = []
          self.install_dirs = []
          self.dep_manifest_name = None
          self.dep_manifest = {}
          self.cross_stdlibs = {}
          self.test_setups = {}
          self.find_overrides = {}
          self.searched_programs = set() # The list of all programs that have been searched for.
  
      def add_compiler(self, compiler):
          if self.static_linker is None and compiler.needs_static_linker():
              self.static_linker = self.environment.detect_static_linker(compiler)
          lang = compiler.get_language()
          if lang not in self.compilers:
              self.compilers[lang] = compiler
  
      def add_cross_compiler(self, compiler):
          if not self.cross_compilers:
              self.static_cross_linker = self.environment.detect_static_linker(compiler)
          lang = compiler.get_language()
          if lang not in self.cross_compilers:
              self.cross_compilers[lang] = compiler
  
      def get_project(self):
          return self.projects['']
  
      def get_subproject_dir(self):
          return self.subproject_dir
  
      def get_targets(self):
          return self.targets
  
      def get_tests(self):
          return self.tests
  
      def get_benchmarks(self):
          return self.benchmarks
  
      def get_headers(self):
          return self.headers
  
      def get_man(self):
          return self.man
  
      def get_data(self):
          return self.data
  
      def get_install_subdirs(self):
          return self.install_dirs
  
      def get_global_args(self, compiler):
          return self.global_args.get(compiler.get_language(), [])
  
      def get_project_args(self, compiler, project):
          args = self.projects_args.get(project)
          if not args:
              return []
          return args.get(compiler.get_language(), [])
  
      def get_global_link_args(self, compiler):
          return self.global_link_args.get(compiler.get_language(), [])
  
      def get_project_link_args(self, compiler, project):
          link_args = self.projects_link_args.get(project)
          if not link_args:
              return []
  
          return link_args.get(compiler.get_language(), [])
  
  class IncludeDirs:
      def __init__(self, curdir, dirs, is_system, extra_build_dirs=None):
          self.curdir = curdir
          self.incdirs = dirs
          self.is_system = is_system
          # Interpreter has validated that all given directories
          # actually exist.
          if extra_build_dirs is None:
              self.extra_build_dirs = []
          else:
              self.extra_build_dirs = extra_build_dirs
  
      def __repr__(self):
          r = '<{} {}/{}>'
          return r.format(self.__class__.__name__, self.curdir, self.incdirs)
  
      def get_curdir(self):
          return self.curdir
  
      def get_incdirs(self):
          return self.incdirs
  
      def get_extra_build_dirs(self):
          return self.extra_build_dirs
  
  class ExtractedObjects:
      '''
      Holds a list of sources for which the objects must be extracted
      '''
      def __init__(self, target, srclist=[], genlist=[], objlist=[], recursive=True):
          self.target = target
          self.recursive = recursive
          self.srclist = srclist
          self.genlist = genlist
          self.objlist = objlist
          if self.target.is_unity:
              self.check_unity_compatible()
  
      def __repr__(self):
          r = '<{0} {1!r}: {2}>'
          return r.format(self.__class__.__name__, self.target.name, self.srclist)
  
      def classify_all_sources(self, sources, generated_sources):
          # Merge sources and generated sources
          sources = list(sources)
          for gensrc in generated_sources:
              for s in gensrc.get_outputs():
                  # We cannot know the path where this source will be generated,
                  # but all we need here is the file extension to determine the
                  # compiler.
                  sources.append(s)
  
          # Filter out headers and all non-source files
          sources = [s for s in sources if environment.is_source(s) and not environment.is_header(s)]
  
          return classify_unity_sources(self.target.compilers.values(), sources)
  
      def check_unity_compatible(self):
          # Figure out if the extracted object list is compatible with a Unity
          # build. When we're doing a Unified build, we go through the sources,
          # and create a single source file from each subset of the sources that
          # can be compiled with a specific compiler. Then we create one object
          # from each unified source file. So for each compiler we can either
          # extra all its sources or none.
          cmpsrcs = self.classify_all_sources(self.target.sources, self.target.generated)
          extracted_cmpsrcs = self.classify_all_sources(self.srclist, self.genlist)
  
          for comp, srcs in extracted_cmpsrcs.items():
              if set(srcs) != set(cmpsrcs[comp]):
                  raise MesonException('Single object files can not be extracted '
                                       'in Unity builds. You can only extract all '
                                       'the object files for each compiler at once.')
  
  class EnvironmentVariables:
      def __init__(self):
          self.envvars = []
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.envvars)
  
      def get_value(self, values, kwargs):
          separator = kwargs.get('separator', os.pathsep)
  
          value = ''
          for var in values:
              value += separator + var
          return separator, value.strip(separator)
  
      def set(self, env, name, values, kwargs):
          return self.get_value(values, kwargs)[1]
  
      def append(self, env, name, values, kwargs):
          sep, value = self.get_value(values, kwargs)
          if name in env:
              return env[name] + sep + value
          return value
  
      def prepend(self, env, name, values, kwargs):
          sep, value = self.get_value(values, kwargs)
          if name in env:
              return value + sep + env[name]
  
          return value
  
      def get_env(self, full_env):
          env = full_env.copy()
          for method, name, values, kwargs in self.envvars:
              env[name] = method(full_env, name, values, kwargs)
          return env
  
  class Target:
      def __init__(self, name, subdir, subproject, build_by_default):
          if has_path_sep(name):
              # Fix failing test 53 when this becomes an error.
              mlog.warning('''Target "%s" has a path separator in its name.
  This is not supported, it can cause unexpected failures and will become
  a hard error in the future.''' % name)
          self.name = name
          self.subdir = subdir
          self.subproject = subproject
          self.build_by_default = build_by_default
          self.install = False
          self.build_always = False
          self.option_overrides = {}
  
      def get_basename(self):
          return self.name
  
      def get_subdir(self):
          return self.subdir
  
      def get_id(self):
          # This ID must also be a valid file name on all OSs.
          # It should also avoid shell metacharacters for obvious
          # reasons. '@' is not used as often as '_' in source code names.
          # In case of collisions consider using checksums.
          # FIXME replace with assert when slash in names is prohibited
          name_part = self.name.replace('/', '@').replace('\\', '@')
          assert not has_path_sep(self.type_suffix())
          myid = name_part + self.type_suffix()
          if self.subdir:
              subdir_part = self.subdir.replace('/', '@').replace('\\', '@')
              myid = subdir_part + '@@' + myid
          return myid
  
      @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
      @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
      @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
      def process_kwargs(self, kwargs):
          if 'build_by_default' in kwargs:
              self.build_by_default = kwargs['build_by_default']
              if not isinstance(self.build_by_default, bool):
                  raise InvalidArguments('build_by_default must be a boolean value.')
          self.option_overrides = self.parse_overrides(kwargs)
  
      def parse_overrides(self, kwargs):
          result = {}
          overrides = stringlistify(kwargs.get('override_options', []))
          for o in overrides:
              if '=' not in o:
                  raise InvalidArguments('Overrides must be of form "key=value"')
              k, v = o.split('=', 1)
              k = k.strip()
              v = v.strip()
              result[k] = v
          return result
  
  
  class BuildTarget(Target):
      known_kwargs = known_build_target_kwargs
  
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, True)
          self.is_cross = is_cross
          unity_opt = environment.coredata.get_builtin_option('unity')
          self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
          self.environment = environment
          self.sources = []
          self.compilers = OrderedDict()
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_whole_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The list of all files outputted by this target. Useful in cases such
          # as Vala which generates .vapi and .h besides the compiled output.
          self.outputs = [self.filename]
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          self.d_features = {}
          # Sources can be:
          # 1. Pre-existing source files in the source tree
          # 2. Pre-existing sources generated by configure_file in the build tree
          # 3. Sources files generated by another target or a Generator
          self.process_sourcelist(sources)
          # Objects can be:
          # 1. Pre-existing objects provided by the user with the `objects:` kwarg
          # 2. Compiled objects created by and extracted from another target
          self.process_objectlist(objects)
          self.process_kwargs(kwargs, environment)
          self.check_unknown_kwargs(kwargs)
          self.process_compilers()
          if not any([self.sources, self.generated, self.objects, self.link_whole]):
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers_late()
          self.validate_sources()
          self.validate_cross_install(environment)
          self.check_module_linking()
  
      def __lt__(self, other):
          return self.get_id() < other.get_id()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      def validate_cross_install(self, environment):
          if environment.is_cross_build() and not self.is_cross and self.install:
              raise InvalidArguments('Tried to install a natively built target in a cross build.')
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, self.known_kwargs)
  
      def check_unknown_kwargs_int(self, kwargs, known_kwargs):
          unknowns = []
          for k in kwargs:
              if k not in known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword argument(s) in target %s: %s.' %
                           (self.name, ', '.join(unknowns)))
  
      def process_objectlist(self, objects):
          assert(isinstance(objects, list))
          for s in objects:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, (str, File, ExtractedObjects)):
                  self.objects.append(s)
              elif isinstance(s, (GeneratedList, CustomTarget)):
                  msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
                      'for target {!r}.\nIt is meant only for '.format(self.name) + \
                      'pre-built object files that are shipped with the\nsource ' + \
                      'tree. Try adding it in the list of sources.'
                  raise InvalidArguments(msg)
              else:
                  msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      def process_sourcelist(self, sources):
          sources = listify(sources)
          added_sources = {} # If the same source is defined multiple times, use it only once.
          for s in sources:
              # Holder unpacking. Ugly.
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, File):
                  if s not in added_sources:
                      self.sources.append(s)
                      added_sources[s] = True
              elif isinstance(s, (GeneratedList, CustomTarget, CustomTargetIndex)):
                  self.generated.append(s)
              else:
                  msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      @staticmethod
      def can_compile_remove_sources(compiler, sources):
          removed = False
          for s in sources[:]:
              if compiler.can_compile(s):
                  sources.remove(s)
                  removed = True
          return removed
  
      def process_compilers_late(self):
          """Processes additional compilers after kwargs have been evaluated.
  
          This can add extra compilers that might be required by keyword
          arguments, such as link_with or dependencies. It will also try to guess
          which compiler to use if one hasn't been selected already.
          """
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
  
          # If this library is linked against another library we need to consider
          # the languages of those libraries as well.
          if self.link_targets or self.link_whole_targets:
              extra = set()
              for t in itertools.chain(self.link_targets, self.link_whole_targets):
                  for name, compiler in t.compilers.items():
                      if name in clike_langs:
                          extra.add((name, compiler))
              for name, compiler in sorted(extra, key=lambda p: sort_clike(p[0])):
                  self.compilers[name] = compiler
  
          if not self.compilers:
              # No source files or parent targets, target consists of only object
              # files of unknown origin. Just add the first clike compiler
              # that we have and hope that it can link these objects
              for lang in clike_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
  
      def process_compilers(self):
          '''
          Populate self.compilers, which is the list of compilers that this
          target will use for compiling all its sources.
          We also add compilers that were used by extracted objects to simplify
          dynamic linker determination.
          '''
          if not self.sources and not self.generated and not self.objects:
              return
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
          # Pre-existing sources
          sources = list(self.sources)
          # All generated sources
          for gensrc in self.generated:
              for s in gensrc.get_outputs():
                  # Generated objects can't be compiled, so don't use them for
                  # compiler detection. If our target only has generated objects,
                  # we will fall back to using the first c-like compiler we find,
                  # which is what we need.
                  if not is_object(s):
                      sources.append(s)
          for d in self.external_deps:
              if hasattr(d, 'held_object'):
                  d = d.held_object
              for s in d.sources:
                  if isinstance(s, (str, File)):
                      sources.append(s)
  
          # Sources that were used to create our extracted objects
          for o in self.objects:
              if not isinstance(o, ExtractedObjects):
                  continue
              for s in o.srclist:
                  # Don't add Vala sources since that will pull in the Vala
                  # compiler even though we will never use it since we are
                  # dealing with compiled C code.
                  if not s.endswith(lang_suffixes['vala']):
                      sources.append(s)
          if sources:
              # For each source, try to add one compiler that can compile it.
              # It's ok if no compilers can do so, because users are expected to
              # be able to add arbitrary non-source files to the sources list.
              for s in sources:
                  for lang, compiler in compilers.items():
                      if compiler.can_compile(s):
                          if lang not in self.compilers:
                              self.compilers[lang] = compiler
                          break
              # Re-sort according to clike_langs
              self.compilers = OrderedDict(sorted(self.compilers.items(),
                                                  key=lambda t: sort_clike(t[0])))
  
          # If all our sources are Vala, our target also needs the C compiler but
          # it won't get added above.
          if 'vala' in self.compilers and 'c' not in self.compilers:
              self.compilers['c'] = compilers['c']
  
      def validate_sources(self):
          if not self.sources:
              return
          for lang in ('cs', 'java'):
              if lang in self.compilers:
                  check_sources = list(self.sources)
                  compiler = self.compilers[lang]
                  if not self.can_compile_remove_sources(compiler, check_sources):
                      m = 'No {} sources found in target {!r}'.format(lang, self.name)
                      raise InvalidArguments(m)
                  if check_sources:
                      m = '{0} targets can only contain {0} files:\n'.format(lang.capitalize())
                      m += '\n'.join([repr(c) for c in check_sources])
                      raise InvalidArguments(m)
                  # CSharp and Java targets can't contain any other file types
                  assert(len(self.compilers) == 1)
                  return
  
      def process_link_depends(self, sources, environment):
          """Process the link_depends keyword argument.
  
          This is designed to handle strings, Files, and the output of Custom
          Targets. Notably it doesn't handle generator() returned objects, since
          adding them as a link depends would inherently cause them to be
          generated twice, since the output needs to be passed to the ld_args and
          link_depends.
          """
          sources = listify(sources)
          for s in sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
  
              if isinstance(s, File):
                  self.link_depends.append(s)
              elif isinstance(s, str):
                  self.link_depends.append(
                      File.from_source_file(environment.source_dir, self.subdir, s))
              elif hasattr(s, 'get_outputs'):
                  self.link_depends.extend(
                      [File.from_built_file(s.subdir, p) for p in s.get_outputs()])
              else:
                  raise InvalidArguments(
                      'Link_depends arguments must be strings, Files, '
                      'or a Custom Target, or lists thereof.')
  
      def get_original_kwargs(self):
          return self.kwargs
  
      def unpack_holder(self, d):
          d = listify(d)
          newd = []
          for i in d:
              if isinstance(i, list):
                  i = self.unpack_holder(i)
              elif hasattr(i, 'held_object'):
                  i = i.held_object
              for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
                  if hasattr(i, t):
                      setattr(i, t, self.unpack_holder(getattr(i, t)))
              newd.append(i)
          return newd
  
      def copy_kwargs(self, kwargs):
          self.kwargs = copy.copy(kwargs)
          # This sucks quite badly. Arguments
          # are holders but they can't be pickled
          # so unpack those known.
          for k, v in self.kwargs.items():
              if isinstance(v, list):
                  self.kwargs[k] = self.unpack_holder(v)
              if hasattr(v, 'held_object'):
                  self.kwargs[k] = v.held_object
          for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
              if t in self.kwargs:
                  self.kwargs[t] = self.unpack_holder(self.kwargs[t])
  
      def extract_objects(self, srclist):
          obj_src = []
          for src in srclist:
              if not isinstance(src, str):
                  raise MesonException('Object extraction arguments must be strings.')
              src = File(False, self.subdir, src)
              # FIXME: It could be a generated source
              if src not in self.sources:
                  raise MesonException('Tried to extract unknown source %s.' % src)
              obj_src.append(src)
          return ExtractedObjects(self, obj_src)
  
      def extract_all_objects(self, recursive=True):
          return ExtractedObjects(self, self.sources, self.generated, self.objects,
                                  recursive)
  
      def get_all_link_deps(self):
          return self.get_transitive_link_deps()
  
      def get_transitive_link_deps(self):
          result = []
          for i in self.link_targets:
              result += i.get_all_link_deps()
          return result
  
      def get_custom_install_dir(self):
          return self.install_dir
  
      def get_custom_install_mode(self):
          return self.install_mode
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs)
          self.copy_kwargs(kwargs)
          kwargs.get('modules', [])
          self.need_install = kwargs.get('install', self.need_install)
          llist = extract_as_list(kwargs, 'link_with')
          for linktarget in llist:
              # Sorry for this hack. Keyword targets are kept in holders
              # in kwargs. Unpack here without looking at the exact type.
              if hasattr(linktarget, "held_object"):
                  linktarget = linktarget.held_object
              if isinstance(linktarget, dependencies.ExternalLibrary):
                  raise MesonException('''An external library was used in link_with keyword argument, which
  is reserved for libraries built as part of this project. External
  libraries must be passed using the dependencies keyword argument
  instead, because they are conceptually "external dependencies",
  just like those detected with the dependency() function.''')
              self.link(linktarget)
          lwhole = extract_as_list(kwargs, 'link_whole')
          for linktarget in lwhole:
              self.link_whole(linktarget)
  
          c_pchlist, cpp_pchlist, clist, cpplist, cslist, valalist,  objclist, objcpplist, fortranlist, rustlist \
              = extract_as_list(kwargs, 'c_pch', 'cpp_pch', 'c_args', 'cpp_args', 'cs_args', 'vala_args', 'objc_args',
                                'objcpp_args', 'fortran_args', 'rust_args')
  
          self.add_pch('c', c_pchlist)
          self.add_pch('cpp', cpp_pchlist)
          compiler_args = {'c': clist, 'cpp': cpplist, 'cs': cslist, 'vala': valalist, 'objc': objclist, 'objcpp': objcpplist,
                           'fortran': fortranlist, 'rust': rustlist
                           }
          for key, value in compiler_args.items():
              self.add_compiler_args(key, value)
  
          if not isinstance(self, Executable) or 'export_dynamic' in kwargs:
              self.vala_header = kwargs.get('vala_header', self.name + '.h')
              self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi')
              self.vala_gir = kwargs.get('vala_gir', None)
  
          dlist = stringlistify(kwargs.get('d_args', []))
          self.add_compiler_args('d', dlist)
          dfeatures = dict()
          dfeature_unittest = kwargs.get('d_unittest', False)
          if dfeature_unittest:
              dfeatures['unittest'] = dfeature_unittest
          dfeature_versions = kwargs.get('d_module_versions', None)
          if dfeature_versions:
              dfeatures['versions'] = dfeature_versions
          if 'd_import_dirs' in kwargs:
              dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs', unholder=True)
              for d in dfeature_import_dirs:
                  if not isinstance(d, IncludeDirs):
                      raise InvalidArguments('Arguments to d_import_dirs must be include_directories.')
              dfeatures['import_dirs'] = dfeature_import_dirs
          if dfeatures:
              self.d_features = dfeatures
  
          self.link_args = extract_as_list(kwargs, 'link_args')
          for i in self.link_args:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_args arguments must be strings.')
          for l in self.link_args:
              if '-Wl,-rpath' in l or l.startswith('-rpath'):
                  mlog.warning('''Please do not define rpath with a linker argument, use install_rpath or build_rpath properties instead.
  This will become a hard error in a future Meson release.''')
          self.process_link_depends(kwargs.get('link_depends', []), environment)
          # Target-specific include dirs must be added BEFORE include dirs from
          # internal deps (added inside self.add_deps()) to override them.
          inclist = extract_as_list(kwargs, 'include_directories')
          self.add_include_dirs(inclist)
          # Add dependencies (which also have include_directories)
          deplist = extract_as_list(kwargs, 'dependencies')
          self.add_deps(deplist)
          # If an item in this list is False, the output corresponding to
          # the list index of that item will not be installed
          self.install_dir = typeslistify(kwargs.get('install_dir', [None]),
                                          (str, bool))
          self.install_mode = kwargs.get('install_mode', None)
          main_class = kwargs.get('main_class', '')
          if not isinstance(main_class, str):
              raise InvalidArguments('Main class must be a string')
          self.main_class = main_class
          if isinstance(self, Executable):
              self.gui_app = kwargs.get('gui_app', False)
              if not isinstance(self.gui_app, bool):
                  raise InvalidArguments('Argument gui_app must be boolean.')
          elif 'gui_app' in kwargs:
              raise InvalidArguments('Argument gui_app can only be used on executables.')
          extra_files = extract_as_list(kwargs, 'extra_files')
          for i in extra_files:
              assert(isinstance(i, File))
              trial = os.path.join(environment.get_source_dir(), i.subdir, i.fname)
              if not(os.path.isfile(trial)):
                  raise InvalidArguments('Tried to add non-existing extra file %s.' % i)
          self.extra_files = extra_files
          self.install_rpath = kwargs.get('install_rpath', '')
          if not isinstance(self.install_rpath, str):
              raise InvalidArguments('Install_rpath is not a string.')
          self.build_rpath = kwargs.get('build_rpath', '')
          if not isinstance(self.build_rpath, str):
              raise InvalidArguments('Build_rpath is not a string.')
          resources = extract_as_list(kwargs, 'resources')
          for r in resources:
              if not isinstance(r, str):
                  raise InvalidArguments('Resource argument is not a string.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, r)
              if not os.path.isfile(trial):
                  raise InvalidArguments('Tried to add non-existing resource %s.' % r)
          self.resources = resources
          if 'name_prefix' in kwargs:
              name_prefix = kwargs['name_prefix']
              if isinstance(name_prefix, list):
                  if name_prefix:
                      raise InvalidArguments('name_prefix array must be empty to signify null.')
              elif not isinstance(name_prefix, str):
                  raise InvalidArguments('name_prefix must be a string.')
              self.prefix = name_prefix
              self.name_prefix_set = True
          if 'name_suffix' in kwargs:
              name_suffix = kwargs['name_suffix']
              if isinstance(name_suffix, list):
                  if name_suffix:
                      raise InvalidArguments('name_suffix array must be empty to signify null.')
              else:
                  if not isinstance(name_suffix, str):
                      raise InvalidArguments('name_suffix must be a string.')
                  if name_suffix == '':
                      raise InvalidArguments('name_suffix should not be an empty string. '
                                             'If you want meson to use the default behaviour '
                                             'for each platform pass `[]` (empty array)')
                  self.suffix = name_suffix
                  self.name_suffix_set = True
          if isinstance(self, StaticLibrary):
              # You can't disable PIC on OS X. The compiler ignores -fno-PIC.
              # PIC is always on for Windows (all code is position-independent
              # since library loading is done differently)
              if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment):
                  self.pic = True
              elif '-fPIC' in clist + cpplist:
                  mlog.warning("Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name))
                  self.pic = True
              else:
                  self.pic = kwargs.get('pic', False)
                  if not isinstance(self.pic, bool):
                      raise InvalidArguments('Argument pic to static library {!r} must be boolean'.format(self.name))
          self.implicit_include_directories = kwargs.get('implicit_include_directories', True)
          if not isinstance(self.implicit_include_directories, bool):
              raise InvalidArguments('Implicit_include_directories must be a boolean.')
  
      def get_filename(self):
          return self.filename
  
      def get_outputs(self):
          return self.outputs
  
      def get_extra_args(self, language):
          return self.extra_args.get(language, [])
  
      def get_dependencies(self, exclude=None, internal=True):
          transitive_deps = []
          if exclude is None:
              exclude = []
          if internal:
              link_targets = itertools.chain
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/build.py
files.old_contents: # Copyright 2012-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.
  
  import copy, os, re
  from collections import OrderedDict
  import itertools, pathlib
  import pickle
  
  from . import environment
  from . import dependencies
  from . import mlog
  from .mesonlib import File, MesonException, listify, extract_as_list
  from .mesonlib import typeslistify, stringlistify, classify_unity_sources
  from .mesonlib import get_filenames_templates_dict, substitute_values
  from .mesonlib import for_windows, for_darwin, for_cygwin, for_android, has_path_sep
  from .compilers import is_object, clike_langs, sort_clike, lang_suffixes
  from .interpreterbase import FeatureNew, FeatureNewKwargs
  
  pch_kwargs = set(['c_pch', 'cpp_pch'])
  
  lang_arg_kwargs = set([
      'c_args',
      'cpp_args',
      'd_args',
      'd_import_dirs',
      'd_unittest',
      'd_module_versions',
      'fortran_args',
      'java_args',
      'objc_args',
      'objcpp_args',
      'rust_args',
      'vala_args',
      'cs_args',
  ])
  
  vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi'])
  rust_kwargs = set(['rust_crate_type'])
  cs_kwargs = set(['resources', 'cs_args'])
  
  buildtarget_kwargs = set([
      'build_by_default',
      'build_rpath',
      'dependencies',
      'extra_files',
      'gui_app',
      'link_with',
      'link_whole',
      'link_args',
      'link_depends',
      'implicit_include_directories',
      'include_directories',
      'install',
      'install_rpath',
      'install_dir',
      'name_prefix',
      'name_suffix',
      'native',
      'objects',
      'override_options',
      'sources',
  ])
  
  known_build_target_kwargs = (
      buildtarget_kwargs |
      lang_arg_kwargs |
      pch_kwargs |
      vala_kwargs |
      rust_kwargs |
      cs_kwargs)
  
  known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic'}
  known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs'}
  known_shmod_kwargs = known_build_target_kwargs
  known_stlib_kwargs = known_build_target_kwargs | {'pic'}
  known_jar_kwargs = known_exe_kwargs | {'main_class'}
  
  class InvalidArguments(MesonException):
      pass
  
  class Build:
      """A class that holds the status of one build including
      all dependencies and so on.
      """
  
      def __init__(self, environment):
          self.project_name = 'name of master project'
          self.project_version = None
          self.environment = environment
          self.projects = {}
          self.targets = OrderedDict()
          self.compilers = OrderedDict()
          self.cross_compilers = OrderedDict()
          self.global_args = {}
          self.projects_args = {}
          self.global_link_args = {}
          self.projects_link_args = {}
          self.tests = []
          self.benchmarks = []
          self.headers = []
          self.man = []
          self.data = []
          self.static_linker = None
          self.static_cross_linker = None
          self.subprojects = {}
          self.subproject_dir = ''
          self.install_scripts = []
          self.postconf_scripts = []
          self.install_dirs = []
          self.dep_manifest_name = None
          self.dep_manifest = {}
          self.cross_stdlibs = {}
          self.test_setups = {}
          self.find_overrides = {}
          self.searched_programs = set() # The list of all programs that have been searched for.
  
      def add_compiler(self, compiler):
          if self.static_linker is None and compiler.needs_static_linker():
              self.static_linker = self.environment.detect_static_linker(compiler)
          lang = compiler.get_language()
          if lang not in self.compilers:
              self.compilers[lang] = compiler
  
      def add_cross_compiler(self, compiler):
          if not self.cross_compilers:
              self.static_cross_linker = self.environment.detect_static_linker(compiler)
          lang = compiler.get_language()
          if lang not in self.cross_compilers:
              self.cross_compilers[lang] = compiler
  
      def get_project(self):
          return self.projects['']
  
      def get_subproject_dir(self):
          return self.subproject_dir
  
      def get_targets(self):
          return self.targets
  
      def get_tests(self):
          return self.tests
  
      def get_benchmarks(self):
          return self.benchmarks
  
      def get_headers(self):
          return self.headers
  
      def get_man(self):
          return self.man
  
      def get_data(self):
          return self.data
  
      def get_install_subdirs(self):
          return self.install_dirs
  
      def get_global_args(self, compiler):
          return self.global_args.get(compiler.get_language(), [])
  
      def get_project_args(self, compiler, project):
          args = self.projects_args.get(project)
          if not args:
              return []
          return args.get(compiler.get_language(), [])
  
      def get_global_link_args(self, compiler):
          return self.global_link_args.get(compiler.get_language(), [])
  
      def get_project_link_args(self, compiler, project):
          link_args = self.projects_link_args.get(project)
          if not link_args:
              return []
  
          return link_args.get(compiler.get_language(), [])
  
  class IncludeDirs:
      def __init__(self, curdir, dirs, is_system, extra_build_dirs=None):
          self.curdir = curdir
          self.incdirs = dirs
          self.is_system = is_system
          # Interpreter has validated that all given directories
          # actually exist.
          if extra_build_dirs is None:
              self.extra_build_dirs = []
          else:
              self.extra_build_dirs = extra_build_dirs
  
      def __repr__(self):
          r = '<{} {}/{}>'
          return r.format(self.__class__.__name__, self.curdir, self.incdirs)
  
      def get_curdir(self):
          return self.curdir
  
      def get_incdirs(self):
          return self.incdirs
  
      def get_extra_build_dirs(self):
          return self.extra_build_dirs
  
  class ExtractedObjects:
      '''
      Holds a list of sources for which the objects must be extracted
      '''
      def __init__(self, target, srclist=[], genlist=[], objlist=[], recursive=True):
          self.target = target
          self.recursive = recursive
          self.srclist = srclist
          self.genlist = genlist
          self.objlist = objlist
          if self.target.is_unity:
              self.check_unity_compatible()
  
      def __repr__(self):
          r = '<{0} {1!r}: {2}>'
          return r.format(self.__class__.__name__, self.target.name, self.srclist)
  
      def classify_all_sources(self, sources, generated_sources):
          # Merge sources and generated sources
          sources = list(sources)
          for gensrc in generated_sources:
              for s in gensrc.get_outputs():
                  # We cannot know the path where this source will be generated,
                  # but all we need here is the file extension to determine the
                  # compiler.
                  sources.append(s)
  
          # Filter out headers and all non-source files
          sources = [s for s in sources if environment.is_source(s) and not environment.is_header(s)]
  
          return classify_unity_sources(self.target.compilers.values(), sources)
  
      def check_unity_compatible(self):
          # Figure out if the extracted object list is compatible with a Unity
          # build. When we're doing a Unified build, we go through the sources,
          # and create a single source file from each subset of the sources that
          # can be compiled with a specific compiler. Then we create one object
          # from each unified source file. So for each compiler we can either
          # extra all its sources or none.
          cmpsrcs = self.classify_all_sources(self.target.sources, self.target.generated)
          extracted_cmpsrcs = self.classify_all_sources(self.srclist, self.genlist)
  
          for comp, srcs in extracted_cmpsrcs.items():
              if set(srcs) != set(cmpsrcs[comp]):
                  raise MesonException('Single object files can not be extracted '
                                       'in Unity builds. You can only extract all '
                                       'the object files for each compiler at once.')
  
  class EnvironmentVariables:
      def __init__(self):
          self.envvars = []
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.envvars)
  
      def get_value(self, values, kwargs):
          separator = kwargs.get('separator', os.pathsep)
  
          value = ''
          for var in values:
              value += separator + var
          return separator, value.strip(separator)
  
      def set(self, env, name, values, kwargs):
          return self.get_value(values, kwargs)[1]
  
      def append(self, env, name, values, kwargs):
          sep, value = self.get_value(values, kwargs)
          if name in env:
              return env[name] + sep + value
          return value
  
      def prepend(self, env, name, values, kwargs):
          sep, value = self.get_value(values, kwargs)
          if name in env:
              return value + sep + env[name]
  
          return value
  
      def get_env(self, full_env):
          env = full_env.copy()
          for method, name, values, kwargs in self.envvars:
              env[name] = method(full_env, name, values, kwargs)
          return env
  
  class Target:
      def __init__(self, name, subdir, subproject, build_by_default):
          if has_path_sep(name):
              # Fix failing test 53 when this becomes an error.
              mlog.warning('''Target "%s" has a path separator in its name.
  This is not supported, it can cause unexpected failures and will become
  a hard error in the future.''' % name)
          self.name = name
          self.subdir = subdir
          self.subproject = subproject
          self.build_by_default = build_by_default
          self.install = False
          self.build_always = False
          self.option_overrides = {}
  
      def get_basename(self):
          return self.name
  
      def get_subdir(self):
          return self.subdir
  
      def get_id(self):
          # This ID must also be a valid file name on all OSs.
          # It should also avoid shell metacharacters for obvious
          # reasons. '@' is not used as often as '_' in source code names.
          # In case of collisions consider using checksums.
          # FIXME replace with assert when slash in names is prohibited
          name_part = self.name.replace('/', '@').replace('\\', '@')
          assert not has_path_sep(self.type_suffix())
          myid = name_part + self.type_suffix()
          if self.subdir:
              subdir_part = self.subdir.replace('/', '@').replace('\\', '@')
              myid = subdir_part + '@@' + myid
          return myid
  
      @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
      @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
      @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
      def process_kwargs(self, kwargs):
          if 'build_by_default' in kwargs:
              self.build_by_default = kwargs['build_by_default']
              if not isinstance(self.build_by_default, bool):
                  raise InvalidArguments('build_by_default must be a boolean value.')
          self.option_overrides = self.parse_overrides(kwargs)
  
      def parse_overrides(self, kwargs):
          result = {}
          overrides = stringlistify(kwargs.get('override_options', []))
          for o in overrides:
              if '=' not in o:
                  raise InvalidArguments('Overrides must be of form "key=value"')
              k, v = o.split('=', 1)
              k = k.strip()
              v = v.strip()
              result[k] = v
          return result
  
  
  class BuildTarget(Target):
      known_kwargs = known_build_target_kwargs
  
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, True)
          self.is_cross = is_cross
          unity_opt = environment.coredata.get_builtin_option('unity')
          self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
          self.environment = environment
          self.sources = []
          self.compilers = OrderedDict()
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_whole_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The list of all files outputted by this target. Useful in cases such
          # as Vala which generates .vapi and .h besides the compiled output.
          self.outputs = [self.filename]
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          self.d_features = {}
          # Sources can be:
          # 1. Pre-existing source files in the source tree
          # 2. Pre-existing sources generated by configure_file in the build tree
          # 3. Sources files generated by another target or a Generator
          self.process_sourcelist(sources)
          # Objects can be:
          # 1. Pre-existing objects provided by the user with the `objects:` kwarg
          # 2. Compiled objects created by and extracted from another target
          self.process_objectlist(objects)
          self.process_kwargs(kwargs, environment)
          self.check_unknown_kwargs(kwargs)
          self.process_compilers()
          if not any([self.sources, self.generated, self.objects, self.link_whole]):
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers_late()
          self.validate_sources()
          self.validate_cross_install(environment)
          self.check_module_linking()
  
      def __lt__(self, other):
          return self.get_id() < other.get_id()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      def validate_cross_install(self, environment):
          if environment.is_cross_build() and not self.is_cross and self.install:
              raise InvalidArguments('Tried to install a natively built target in a cross build.')
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, self.known_kwargs)
  
      def check_unknown_kwargs_int(self, kwargs, known_kwargs):
          unknowns = []
          for k in kwargs:
              if k not in known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword argument(s) in target %s: %s.' %
                           (self.name, ', '.join(unknowns)))
  
      def process_objectlist(self, objects):
          assert(isinstance(objects, list))
          for s in objects:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, (str, File, ExtractedObjects)):
                  self.objects.append(s)
              elif isinstance(s, (GeneratedList, CustomTarget)):
                  msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
                      'for target {!r}.\nIt is meant only for '.format(self.name) + \
                      'pre-built object files that are shipped with the\nsource ' + \
                      'tree. Try adding it in the list of sources.'
                  raise InvalidArguments(msg)
              else:
                  msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      def process_sourcelist(self, sources):
          sources = listify(sources)
          added_sources = {} # If the same source is defined multiple times, use it only once.
          for s in sources:
              # Holder unpacking. Ugly.
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, File):
                  if s not in added_sources:
                      self.sources.append(s)
                      added_sources[s] = True
              elif isinstance(s, (GeneratedList, CustomTarget, CustomTargetIndex)):
                  self.generated.append(s)
              else:
                  msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      @staticmethod
      def can_compile_remove_sources(compiler, sources):
          removed = False
          for s in sources[:]:
              if compiler.can_compile(s):
                  sources.remove(s)
                  removed = True
          return removed
  
      def process_compilers_late(self):
          """Processes additional compilers after kwargs have been evaluated.
  
          This can add extra compilers that might be required by keyword
          arguments, such as link_with or dependencies. It will also try to guess
          which compiler to use if one hasn't been selected already.
          """
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
  
          # If this library is linked against another library we need to consider
          # the languages of those libraries as well.
          if self.link_targets or self.link_whole_targets:
              extra = set()
              for t in itertools.chain(self.link_targets, self.link_whole_targets):
                  for name, compiler in t.compilers.items():
                      if name in clike_langs:
                          extra.add((name, compiler))
              for name, compiler in sorted(extra, key=lambda p: sort_clike(p[0])):
                  self.compilers[name] = compiler
  
          if not self.compilers:
              # No source files or parent targets, target consists of only object
              # files of unknown origin. Just add the first clike compiler
              # that we have and hope that it can link these objects
              for lang in clike_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
  
      def process_compilers(self):
          '''
          Populate self.compilers, which is the list of compilers that this
          target will use for compiling all its sources.
          We also add compilers that were used by extracted objects to simplify
          dynamic linker determination.
          '''
          if not self.sources and not self.generated and not self.objects:
              return
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
          # Pre-existing sources
          sources = list(self.sources)
          # All generated sources
          for gensrc in self.generated:
              for s in gensrc.get_outputs():
                  # Generated objects can't be compiled, so don't use them for
                  # compiler detection. If our target only has generated objects,
                  # we will fall back to using the first c-like compiler we find,
                  # which is what we need.
                  if not is_object(s):
                      sources.append(s)
          for d in self.external_deps:
              if hasattr(d, 'held_object'):
                  d = d.held_object
              for s in d.sources:
                  if isinstance(s, (str, File)):
                      sources.append(s)
  
          # Sources that were used to create our extracted objects
          for o in self.objects:
              if not isinstance(o, ExtractedObjects):
                  continue
              for s in o.srclist:
                  # Don't add Vala sources since that will pull in the Vala
                  # compiler even though we will never use it since we are
                  # dealing with compiled C code.
                  if not s.endswith(lang_suffixes['vala']):
                      sources.append(s)
          if sources:
              # For each source, try to add one compiler that can compile it.
              # It's ok if no compilers can do so, because users are expected to
              # be able to add arbitrary non-source files to the sources list.
              for s in sources:
                  for lang, compiler in compilers.items():
                      if compiler.can_compile(s):
                          if lang not in self.compilers:
                              self.compilers[lang] = compiler
                          break
              # Re-sort according to clike_langs
              self.compilers = OrderedDict(sorted(self.compilers.items(),
                                                  key=lambda t: sort_clike(t[0])))
  
          # If all our sources are Vala, our target also needs the C compiler but
          # it won't get added above.
          if 'vala' in self.compilers and 'c' not in self.compilers:
              self.compilers['c'] = compilers['c']
  
      def validate_sources(self):
          if not self.sources:
              return
          for lang in ('cs', 'java'):
              if lang in self.compilers:
                  check_sources = list(self.sources)
                  compiler = self.compilers[lang]
                  if not self.can_compile_remove_sources(compiler, check_sources):
                      m = 'No {} sources found in target {!r}'.format(lang, self.name)
                      raise InvalidArguments(m)
                  if check_sources:
                      m = '{0} targets can only contain {0} files:\n'.format(lang.capitalize())
                      m += '\n'.join([repr(c) for c in check_sources])
                      raise InvalidArguments(m)
                  # CSharp and Java targets can't contain any other file types
                  assert(len(self.compilers) == 1)
                  return
  
      def process_link_depends(self, sources, environment):
          """Process the link_depends keyword argument.
  
          This is designed to handle strings, Files, and the output of Custom
          Targets. Notably it doesn't handle generator() returned objects, since
          adding them as a link depends would inherently cause them to be
          generated twice, since the output needs to be passed to the ld_args and
          link_depends.
          """
          sources = listify(sources)
          for s in sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
  
              if isinstance(s, File):
                  self.link_depends.append(s)
              elif isinstance(s, str):
                  self.link_depends.append(
                      File.from_source_file(environment.source_dir, self.subdir, s))
              elif hasattr(s, 'get_outputs'):
                  self.link_depends.extend(
                      [File.from_built_file(s.subdir, p) for p in s.get_outputs()])
              else:
                  raise InvalidArguments(
                      'Link_depends arguments must be strings, Files, '
                      'or a Custom Target, or lists thereof.')
  
      def get_original_kwargs(self):
          return self.kwargs
  
      def unpack_holder(self, d):
          d = listify(d)
          newd = []
          for i in d:
              if isinstance(i, list):
                  i = self.unpack_holder(i)
              elif hasattr(i, 'held_object'):
                  i = i.held_object
              for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
                  if hasattr(i, t):
                      setattr(i, t, self.unpack_holder(getattr(i, t)))
              newd.append(i)
          return newd
  
      def copy_kwargs(self, kwargs):
          self.kwargs = copy.copy(kwargs)
          # This sucks quite badly. Arguments
          # are holders but they can't be pickled
          # so unpack those known.
          for k, v in self.kwargs.items():
              if isinstance(v, list):
                  self.kwargs[k] = self.unpack_holder(v)
              if hasattr(v, 'held_object'):
                  self.kwargs[k] = v.held_object
          for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
              if t in self.kwargs:
                  self.kwargs[t] = self.unpack_holder(self.kwargs[t])
  
      def extract_objects(self, srclist):
          obj_src = []
          for src in srclist:
              if not isinstance(src, str):
                  raise MesonException('Object extraction arguments must be strings.')
              src = File(False, self.subdir, src)
              # FIXME: It could be a generated source
              if src not in self.sources:
                  raise MesonException('Tried to extract unknown source %s.' % src)
              obj_src.append(src)
          return ExtractedObjects(self, obj_src)
  
      def extract_all_objects(self, recursive=True):
          return ExtractedObjects(self, self.sources, self.generated, self.objects,
                                  recursive)
  
      def get_all_link_deps(self):
          return self.get_transitive_link_deps()
  
      def get_transitive_link_deps(self):
          result = []
          for i in self.link_targets:
              result += i.get_all_link_deps()
          return result
  
      def get_custom_install_dir(self):
          return self.install_dir
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs)
          self.copy_kwargs(kwargs)
          kwargs.get('modules', [])
          self.need_install = kwargs.get('install', self.need_install)
          llist = extract_as_list(kwargs, 'link_with')
          for linktarget in llist:
              # Sorry for this hack. Keyword targets are kept in holders
              # in kwargs. Unpack here without looking at the exact type.
              if hasattr(linktarget, "held_object"):
                  linktarget = linktarget.held_object
              if isinstance(linktarget, dependencies.ExternalLibrary):
                  raise MesonException('''An external library was used in link_with keyword argument, which
  is reserved for libraries built as part of this project. External
  libraries must be passed using the dependencies keyword argument
  instead, because they are conceptually "external dependencies",
  just like those detected with the dependency() function.''')
              self.link(linktarget)
          lwhole = extract_as_list(kwargs, 'link_whole')
          for linktarget in lwhole:
              self.link_whole(linktarget)
  
          c_pchlist, cpp_pchlist, clist, cpplist, cslist, valalist,  objclist, objcpplist, fortranlist, rustlist \
              = extract_as_list(kwargs, 'c_pch', 'cpp_pch', 'c_args', 'cpp_args', 'cs_args', 'vala_args', 'objc_args',
                                'objcpp_args', 'fortran_args', 'rust_args')
  
          self.add_pch('c', c_pchlist)
          self.add_pch('cpp', cpp_pchlist)
          compiler_args = {'c': clist, 'cpp': cpplist, 'cs': cslist, 'vala': valalist, 'objc': objclist, 'objcpp': objcpplist,
                           'fortran': fortranlist, 'rust': rustlist
                           }
          for key, value in compiler_args.items():
              self.add_compiler_args(key, value)
  
          if not isinstance(self, Executable) or 'export_dynamic' in kwargs:
              self.vala_header = kwargs.get('vala_header', self.name + '.h')
              self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi')
              self.vala_gir = kwargs.get('vala_gir', None)
  
          dlist = stringlistify(kwargs.get('d_args', []))
          self.add_compiler_args('d', dlist)
          dfeatures = dict()
          dfeature_unittest = kwargs.get('d_unittest', False)
          if dfeature_unittest:
              dfeatures['unittest'] = dfeature_unittest
          dfeature_versions = kwargs.get('d_module_versions', None)
          if dfeature_versions:
              dfeatures['versions'] = dfeature_versions
          if 'd_import_dirs' in kwargs:
              dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs', unholder=True)
              for d in dfeature_import_dirs:
                  if not isinstance(d, IncludeDirs):
                      raise InvalidArguments('Arguments to d_import_dirs must be include_directories.')
              dfeatures['import_dirs'] = dfeature_import_dirs
          if dfeatures:
              self.d_features = dfeatures
  
          self.link_args = extract_as_list(kwargs, 'link_args')
          for i in self.link_args:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_args arguments must be strings.')
          for l in self.link_args:
              if '-Wl,-rpath' in l or l.startswith('-rpath'):
                  mlog.warning('''Please do not define rpath with a linker argument, use install_rpath or build_rpath properties instead.
  This will become a hard error in a future Meson release.''')
          self.process_link_depends(kwargs.get('link_depends', []), environment)
          # Target-specific include dirs must be added BEFORE include dirs from
          # internal deps (added inside self.add_deps()) to override them.
          inclist = extract_as_list(kwargs, 'include_directories')
          self.add_include_dirs(inclist)
          # Add dependencies (which also have include_directories)
          deplist = extract_as_list(kwargs, 'dependencies')
          self.add_deps(deplist)
          # If an item in this list is False, the output corresponding to
          # the list index of that item will not be installed
          self.install_dir = typeslistify(kwargs.get('install_dir', [None]),
                                          (str, bool))
          main_class = kwargs.get('main_class', '')
          if not isinstance(main_class, str):
              raise InvalidArguments('Main class must be a string')
          self.main_class = main_class
          if isinstance(self, Executable):
              self.gui_app = kwargs.get('gui_app', False)
              if not isinstance(self.gui_app, bool):
                  raise InvalidArguments('Argument gui_app must be boolean.')
          elif 'gui_app' in kwargs:
              raise InvalidArguments('Argument gui_app can only be used on executables.')
          extra_files = extract_as_list(kwargs, 'extra_files')
          for i in extra_files:
              assert(isinstance(i, File))
              trial = os.path.join(environment.get_source_dir(), i.subdir, i.fname)
              if not(os.path.isfile(trial)):
                  raise InvalidArguments('Tried to add non-existing extra file %s.' % i)
          self.extra_files = extra_files
          self.install_rpath = kwargs.get('install_rpath', '')
          if not isinstance(self.install_rpath, str):
              raise InvalidArguments('Install_rpath is not a string.')
          self.build_rpath = kwargs.get('build_rpath', '')
          if not isinstance(self.build_rpath, str):
              raise InvalidArguments('Build_rpath is not a string.')
          resources = extract_as_list(kwargs, 'resources')
          for r in resources:
              if not isinstance(r, str):
                  raise InvalidArguments('Resource argument is not a string.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, r)
              if not os.path.isfile(trial):
                  raise InvalidArguments('Tried to add non-existing resource %s.' % r)
          self.resources = resources
          if 'name_prefix' in kwargs:
              name_prefix = kwargs['name_prefix']
              if isinstance(name_prefix, list):
                  if name_prefix:
                      raise InvalidArguments('name_prefix array must be empty to signify null.')
              elif not isinstance(name_prefix, str):
                  raise InvalidArguments('name_prefix must be a string.')
              self.prefix = name_prefix
              self.name_prefix_set = True
          if 'name_suffix' in kwargs:
              name_suffix = kwargs['name_suffix']
              if isinstance(name_suffix, list):
                  if name_suffix:
                      raise InvalidArguments('name_suffix array must be empty to signify null.')
              else:
                  if not isinstance(name_suffix, str):
                      raise InvalidArguments('name_suffix must be a string.')
                  if name_suffix == '':
                      raise InvalidArguments('name_suffix should not be an empty string. '
                                             'If you want meson to use the default behaviour '
                                             'for each platform pass `[]` (empty array)')
                  self.suffix = name_suffix
                  self.name_suffix_set = True
          if isinstance(self, StaticLibrary):
              # You can't disable PIC on OS X. The compiler ignores -fno-PIC.
              # PIC is always on for Windows (all code is position-independent
              # since library loading is done differently)
              if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment):
                  self.pic = True
              elif '-fPIC' in clist + cpplist:
                  mlog.warning("Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name))
                  self.pic = True
              else:
                  self.pic = kwargs.get('pic', False)
                  if not isinstance(self.pic, bool):
                      raise InvalidArguments('Argument pic to static library {!r} must be boolean'.format(self.name))
          self.implicit_include_directories = kwargs.get('implicit_include_directories', True)
          if not isinstance(self.implicit_include_directories, bool):
              raise InvalidArguments('Implicit_include_directories must be a boolean.')
  
      def get_filename(self):
          return self.filename
  
      def get_outputs(self):
          return self.outputs
  
      def get_extra_args(self, language):
          return self.extra_args.get(language, [])
  
      def get_dependencies(self, exclude=None, internal=True):
          transitive_deps = []
          if exclude is None:
              exclude = []
          if internal:
              link_targets = itertools.chain(self.link_targets, self.link_whole_targets)
          else:
              # We don't want the 'internal' libraries when generating the
              # `Libs:`
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/build.py
files.new_contents: # Copyright 2012-2018 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, Popen_safe, listify, extract_as_list, has_path_sep
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  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
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .modules import ModuleReturnValue
  
  import os, sys, shutil, uuid
  import re, shlex
  from collections import namedtuple
  from pathlib import PurePath
  
  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 ObjectHolder:
      def __init__(self, obj):
          self.held_object = obj
  
      def __repr__(self):
          return '<Holder: {!r}>'.format(self.held_object)
  
  
  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, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          pc, self.stdout, self.stderr = self.run_command(cmd, args, 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, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False):
          command_array = cmd.get_command() + args
          env = {'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(env)
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, env=child_env, cwd=cwd)
              mlog.debug('--- stdout----')
              mlog.debug(o)
              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)
          ObjectHolder.__init__(self, build.ConfigureFile(subdir, sourcename,
                                                          targetname, configuration_data))
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self):
          MutableInterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.EnvironmentVariables())
          self.methods.update({'set': self.set_method,
                               'append': self.append_method,
                               'prepend': self.prepend_method,
                               })
  
      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")
          self.held_object.envvars.append((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):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData())
          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,
                               })
  
      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.log(mlog.red('DEPRECATION:'),
                       '''Passing a list as the single argument to configuration_data.set is deprecated.
  This will become a hard error in the future''')
              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 = args[0]
          val = args[1]
          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('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):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               'get_pkgconfig_variable': self.pkgconfig_method,
                               'get_configtool_variable': self.configtool_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          if self.held_object.type_name == 'internal':
              return True
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @permittedKwargs({'define_variable'})
      def pkgconfig_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_pkgconfig_variable(varname, kwargs)
  
      @FeatureNew('get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class InternalDependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep)
          self.methods.update({'found': self.found_method,
                               'version': self.version_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return True
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class ExternalProgramHolder(InterpreterObject, ObjectHolder):
      def __init__(self, ep):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, ep)
          self.methods.update({'found': self.found_method,
                               'path': self.path_method})
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      @noPosargs
      @permittedKwargs({})
      def path_method(self, args, kwargs):
          return self.held_object.get_path()
  
      def found(self):
          return self.held_object.found()
  
      def get_command(self):
          return self.held_object.get_command()
  
      def get_name(self):
          return self.held_object.get_name()
  
  class ExternalLibraryHolder(InterpreterObject, ObjectHolder):
      def __init__(self, el):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el)
          self.methods.update({'found': self.found_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      def found(self):
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def get_name(self):
          return self.held_object.name
  
      def get_compile_args(self):
          return self.held_object.get_compile_args()
  
      def get_link_args(self):
          return self.held_object.get_link_args()
  
      def get_exe_args(self):
          return self.held_object.get_exe_args()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      def __init__(self, interpreter, args, kwargs):
          InterpreterObject.__init__(self)
          self.interpreter = interpreter
          ObjectHolder.__init__(self, build.Generator(args, kwargs))
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=[]):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  class BuildMachine(InterpreterObject, ObjectHolder):
      def __init__(self, compilers):
          self.compilers = compilers
          InterpreterObject.__init__(self)
          held_object = environment.MachineInfo(environment.detect_system(),
                                                environment.detect_cpu_family(self.compilers),
                                                environment.detect_cpu(self.compilers),
                                                sys.byteorder)
          ObjectHolder.__init__(self, held_object)
          self.methods.update({'system': self.system_method,
                               'cpu_family': self.cpu_family_method,
                               'cpu': self.cpu_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  # This class will provide both host_machine and
  # target_machine
  class CrossMachineInfo(InterpreterObject, ObjectHolder):
      def __init__(self, cross_info):
          InterpreterObject.__init__(self)
          minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'}
          if set(cross_info) < minimum_cross_info:
              raise InterpreterException(
                  'Machine info is currently {}\n'.format(cross_info) +
                  'but is missing {}.'.format(minimum_cross_info - set(cross_info)))
          self.info = cross_info
          minfo = environment.MachineInfo(cross_info['system'],
                                          cross_info['cpu_family'],
                                          cross_info['cpu'],
                                          cross_info['endian'])
          ObjectHolder.__init__(self, minfo)
          self.methods.update({'system': self.system_method,
                               'cpu': self.cpu_method,
                               'cpu_family': self.cpu_family_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  class IncludeDirsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, idobj):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, idobj)
  
  class Headers(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.install_subdir = kwargs.get('subdir', '')
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InterpreterException('Custom_install_dir must be a string.')
  
      def set_install_subdir(self, subdir):
          self.install_subdir = subdir
  
      def get_install_subdir(self):
          return self.install_subdir
  
      def get_sources(self):
          return self.sources
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
  class DataHolder(InterpreterObject, ObjectHolder):
      def __init__(self, data):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, data)
  
      def get_source_subdir(self):
          return self.held_object.source_subdir
  
      def get_sources(self):
          return self.held_object.sources
  
      def get_install_dir(self):
          return self.held_object.install_dir
  
  class InstallDir(InterpreterObject):
      def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory):
          InterpreterObject.__init__(self)
          self.source_subdir = src_subdir
          self.installable_subdir = inst_subdir
          self.install_dir = install_dir
          self.install_mode = install_mode
          self.exclude = exclude
          self.strip_directory = strip_directory
  
  class Man(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.validate_sources()
          self.custom_install_dir = kwargs.get('install_dir', None)
          self.custom_install_mode = kwargs.get('install_mode', None)
          if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str):
              raise InterpreterException('Custom_install_dir must be a string.')
  
      def validate_sources(self):
          for s in self.sources:
              try:
                  num = int(s.split('.')[-1])
              except (IndexError, ValueError):
                  num = 0
              if num < 1 or num > 8:
                  raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_custom_install_mode(self):
          return self.custom_install_mode
  
      def get_sources(self):
          return self.sources
  
  class GeneratedObjectsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, held_object):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, held_object)
  
  class TargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, target, interp):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, target)
          self.interpreter = interp
  
  class BuildTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'extract_objects': self.extract_objects_method,
                               'extract_all_objects': self.extract_all_objects_method,
                               'get_id': self.get_id_method,
                               'outdir': self.outdir_method,
                               'full_path': self.full_path_method,
                               'private_dir_include': self.private_dir_include_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.filename)
  
      def is_cross(self):
          return self.held_object.is_cross()
  
      @noPosargs
      @permittedKwargs({})
      def private_dir_include_method(self, args, kwargs):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [self.interpreter.backend.get_target_private_dir(self.held_object)]))
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @noPosargs
      @permittedKwargs({})
      def outdir_method(self, args, kwargs):
          return self.interpreter.backend.get_target_dir(self.held_object)
  
      @permittedKwargs({})
      def extract_objects_method(self, args, kwargs):
          gobjs = self.held_object.extract_objects(args)
          return GeneratedObjectsHolder(gobjs)
  
      @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive'])
      @noPosargs
      @permittedKwargs({'recursive'})
      def extract_all_objects_method(self, args, kwargs):
          recursive = kwargs.get('recursive', False)
          gobjs = self.held_object.extract_all_objects(recursive)
          if gobjs.objlist and 'recursive' not in kwargs:
              mlog.warning('extract_all_objects called without setting recursive '
                           'keyword argument. Meson currently defaults to '
                           'non-recursive to maintain backward compatibility but '
                           'the default will be changed in the future.')
          return GeneratedObjectsHolder(gobjs)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.held_object.get_id()
  
  class ExecutableHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class StaticLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class SharedLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          # Set to True only when called from self.func_shared_lib().
          target.shared_library_only = False
  
  class BothLibrariesHolder(BuildTargetHolder):
      def __init__(self, shared_holder, static_holder, interp):
          # FIXME: This build target always represents the shared library, but
          # that should be configurable.
          super().__init__(shared_holder.held_object, interp)
          self.shared_holder = shared_holder
          self.static_holder = static_holder
          self.methods.update({'get_shared_lib': self.get_shared_lib_method,
                               'get_static_lib': self.get_static_lib_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}, {}: {}>'
          h1 = self.shared_holder.held_object
          h2 = self.static_holder.held_object
          return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
  
      @noPosargs
      @permittedKwargs({})
      def get_shared_lib_method(self, args, kwargs):
          return self.shared_holder
  
      @noPosargs
      @permittedKwargs({})
      def get_static_lib_method(self, args, kwargs):
          return self.static_holder
  
  class SharedModuleHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class JarHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class CustomTargetIndexHolder(InterpreterObject, ObjectHolder):
      def __init__(self, object_to_hold):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, object_to_hold)
  
  class CustomTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      def __getitem__(self, index):
          return CustomTargetIndexHolder(self.held_object[index])
  
      def __setitem__(self, index, value):
          raise InterpreterException('Cannot set a member of a CustomTarget')
  
      def __delitem__(self, index):
          raise InterpreterException('Cannot delete a member of a CustomTarget')
  
  class RunTargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, name, command, args, dependencies, subdir, subproject):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.RunTarget(name, command, args, dependencies, subdir, subproject))
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
  class Test(InterpreterObject):
      def __init__(self, name, project, suite, exe, depends, is_parallel,
                   cmd_args, env, should_fail, timeout, workdir):
          InterpreterObject.__init__(self)
          self.name = name
          self.suite = suite
          self.project_name = project
          self.exe = exe
          self.depends = depends
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
  
      def get_exe(self):
          return self.exe
  
      def get_name(self):
          return self.name
  
  class SubprojectHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subinterpreter):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, subinterpreter)
          self.methods.update({'get_variable': self.get_variable_method,
                               })
  
      @permittedKwargs({})
      def get_variable_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Get_variable takes one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Get_variable takes a string argument.')
          if varname not in self.held_object.variables:
              raise InvalidArguments('Requested variable "{0}" not found.'.format(varname))
          return self.held_object.variables[varname]
  
  class CompilerHolder(InterpreterObject):
      def __init__(self, compiler, env):
          InterpreterObject.__init__(self)
          self.compiler = compiler
          self.environment = env
          self.methods.update({'compiles': self.compiles_method,
                               'links': self.links_method,
                               'get_id': self.get_id_method,
                               'compute_int': self.compute_int_method,
                               'sizeof': self.sizeof_method,
                               'get_define': self.get_define_method,
                               'check_header': self.check_header_method,
                               'has_header': self.has_header_method,
                               'has_header_symbol': self.has_header_symbol_method,
                               'run': self.run_method,
                               'has_function': self.has_function_method,
                               'has_member': self.has_member_method,
                               'has_members': self.has_members_method,
                               'has_type': self.has_type_method,
                               'alignment': self.alignment_method,
                               'version': self.version_method,
                               'cmd_array': self.cmd_array_method,
                               'find_library': self.find_library_method,
                               'has_argument': self.has_argument_method,
                               'has_multi_arguments': self.has_multi_arguments_method,
                               'get_supported_arguments': self.get_supported_arguments_method,
                               'first_supported_argument': self.first_supported_argument_method,
                               'has_link_argument': self.has_link_argument_method,
                               'has_multi_link_arguments': self.has_multi_link_arguments_method,
                               'get_supported_link_arguments': self.get_supported_link_arguments_method,
                               'first_supported_link_argument': self.first_supported_link_argument_method,
                               'unittest_args': self.unittest_args_method,
                               'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.compiler.version
  
      @noPosargs
      @permittedKwargs({})
      def cmd_array_method(self, args, kwargs):
          return self.compiler.exelist
  
      def determine_args(self, kwargs):
          nobuiltins = kwargs.get('no_builtin_args', False)
          if not isinstance(nobuiltins
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/interpreter.py
files.old_contents: # Copyright 2012-2018 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, Popen_safe, listify, extract_as_list, has_path_sep
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  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
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .modules import ModuleReturnValue
  
  import os, sys, shutil, uuid
  import re, shlex
  from collections import namedtuple
  from pathlib import PurePath
  
  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 ObjectHolder:
      def __init__(self, obj):
          self.held_object = obj
  
      def __repr__(self):
          return '<Holder: {!r}>'.format(self.held_object)
  
  
  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, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          pc, self.stdout, self.stderr = self.run_command(cmd, args, 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, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False):
          command_array = cmd.get_command() + args
          env = {'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(env)
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, env=child_env, cwd=cwd)
              mlog.debug('--- stdout----')
              mlog.debug(o)
              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)
          ObjectHolder.__init__(self, build.ConfigureFile(subdir, sourcename,
                                                          targetname, configuration_data))
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self):
          MutableInterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.EnvironmentVariables())
          self.methods.update({'set': self.set_method,
                               'append': self.append_method,
                               'prepend': self.prepend_method,
                               })
  
      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")
          self.held_object.envvars.append((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):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData())
          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,
                               })
  
      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.log(mlog.red('DEPRECATION:'),
                       '''Passing a list as the single argument to configuration_data.set is deprecated.
  This will become a hard error in the future''')
              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 = args[0]
          val = args[1]
          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('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):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               'get_pkgconfig_variable': self.pkgconfig_method,
                               'get_configtool_variable': self.configtool_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def type_name_method(self, args, kwargs):
          return self.held_object.type_name
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          if self.held_object.type_name == 'internal':
              return True
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @permittedKwargs({'define_variable'})
      def pkgconfig_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_pkgconfig_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_pkgconfig_variable(varname, kwargs)
  
      @FeatureNew('get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class InternalDependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep)
          self.methods.update({'found': self.found_method,
                               'version': self.version_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return True
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.held_object.get_version()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class ExternalProgramHolder(InterpreterObject, ObjectHolder):
      def __init__(self, ep):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, ep)
          self.methods.update({'found': self.found_method,
                               'path': self.path_method})
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      @noPosargs
      @permittedKwargs({})
      def path_method(self, args, kwargs):
          return self.held_object.get_path()
  
      def found(self):
          return self.held_object.found()
  
      def get_command(self):
          return self.held_object.get_command()
  
      def get_name(self):
          return self.held_object.get_name()
  
  class ExternalLibraryHolder(InterpreterObject, ObjectHolder):
      def __init__(self, el):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el)
          self.methods.update({'found': self.found_method,
                               'partial_dependency': self.partial_dependency_method,
                               })
  
      def found(self):
          return self.held_object.found()
  
      @noPosargs
      @permittedKwargs({})
      def found_method(self, args, kwargs):
          return self.found()
  
      def get_name(self):
          return self.held_object.name
  
      def get_compile_args(self):
          return self.held_object.get_compile_args()
  
      def get_link_args(self):
          return self.held_object.get_link_args()
  
      def get_exe_args(self):
          return self.held_object.get_exe_args()
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          return DependencyHolder(self.held_object.get_partial_dependency(**kwargs))
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      def __init__(self, interpreter, args, kwargs):
          InterpreterObject.__init__(self)
          self.interpreter = interpreter
          ObjectHolder.__init__(self, build.Generator(args, kwargs))
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=[]):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  class BuildMachine(InterpreterObject, ObjectHolder):
      def __init__(self, compilers):
          self.compilers = compilers
          InterpreterObject.__init__(self)
          held_object = environment.MachineInfo(environment.detect_system(),
                                                environment.detect_cpu_family(self.compilers),
                                                environment.detect_cpu(self.compilers),
                                                sys.byteorder)
          ObjectHolder.__init__(self, held_object)
          self.methods.update({'system': self.system_method,
                               'cpu_family': self.cpu_family_method,
                               'cpu': self.cpu_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  # This class will provide both host_machine and
  # target_machine
  class CrossMachineInfo(InterpreterObject, ObjectHolder):
      def __init__(self, cross_info):
          InterpreterObject.__init__(self)
          minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'}
          if set(cross_info) < minimum_cross_info:
              raise InterpreterException(
                  'Machine info is currently {}\n'.format(cross_info) +
                  'but is missing {}.'.format(minimum_cross_info - set(cross_info)))
          self.info = cross_info
          minfo = environment.MachineInfo(cross_info['system'],
                                          cross_info['cpu_family'],
                                          cross_info['cpu'],
                                          cross_info['endian'])
          ObjectHolder.__init__(self, minfo)
          self.methods.update({'system': self.system_method,
                               'cpu': self.cpu_method,
                               'cpu_family': self.cpu_family_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  class IncludeDirsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, idobj):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, idobj)
  
  class Headers(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.install_subdir = kwargs.get('subdir', '')
          self.custom_install_dir = kwargs.get('install_dir', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InterpreterException('Custom_install_dir must be a string.')
  
      def set_install_subdir(self, subdir):
          self.install_subdir = subdir
  
      def get_install_subdir(self):
          return self.install_subdir
  
      def get_sources(self):
          return self.sources
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
  class DataHolder(InterpreterObject, ObjectHolder):
      def __init__(self, data):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, data)
  
      def get_source_subdir(self):
          return self.held_object.source_subdir
  
      def get_sources(self):
          return self.held_object.sources
  
      def get_install_dir(self):
          return self.held_object.install_dir
  
  class InstallDir(InterpreterObject):
      def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory):
          InterpreterObject.__init__(self)
          self.source_subdir = src_subdir
          self.installable_subdir = inst_subdir
          self.install_dir = install_dir
          self.install_mode = install_mode
          self.exclude = exclude
          self.strip_directory = strip_directory
  
  class Man(InterpreterObject):
  
      def __init__(self, sources, kwargs):
          InterpreterObject.__init__(self)
          self.sources = sources
          self.validate_sources()
          self.custom_install_dir = kwargs.get('install_dir', None)
          if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str):
              raise InterpreterException('Custom_install_dir must be a string.')
  
      def validate_sources(self):
          for s in self.sources:
              try:
                  num = int(s.split('.')[-1])
              except (IndexError, ValueError):
                  num = 0
              if num < 1 or num > 8:
                  raise InvalidArguments('Man file must have a file extension of a number between 1 and 8')
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def get_sources(self):
          return self.sources
  
  class GeneratedObjectsHolder(InterpreterObject, ObjectHolder):
      def __init__(self, held_object):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, held_object)
  
  class TargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, target, interp):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, target)
          self.interpreter = interp
  
  class BuildTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'extract_objects': self.extract_objects_method,
                               'extract_all_objects': self.extract_all_objects_method,
                               'get_id': self.get_id_method,
                               'outdir': self.outdir_method,
                               'full_path': self.full_path_method,
                               'private_dir_include': self.private_dir_include_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.filename)
  
      def is_cross(self):
          return self.held_object.is_cross()
  
      @noPosargs
      @permittedKwargs({})
      def private_dir_include_method(self, args, kwargs):
          return IncludeDirsHolder(build.IncludeDirs('', [], False,
                                                     [self.interpreter.backend.get_target_private_dir(self.held_object)]))
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      @noPosargs
      @permittedKwargs({})
      def outdir_method(self, args, kwargs):
          return self.interpreter.backend.get_target_dir(self.held_object)
  
      @permittedKwargs({})
      def extract_objects_method(self, args, kwargs):
          gobjs = self.held_object.extract_objects(args)
          return GeneratedObjectsHolder(gobjs)
  
      @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive'])
      @noPosargs
      @permittedKwargs({'recursive'})
      def extract_all_objects_method(self, args, kwargs):
          recursive = kwargs.get('recursive', False)
          gobjs = self.held_object.extract_all_objects(recursive)
          if gobjs.objlist and 'recursive' not in kwargs:
              mlog.warning('extract_all_objects called without setting recursive '
                           'keyword argument. Meson currently defaults to '
                           'non-recursive to maintain backward compatibility but '
                           'the default will be changed in the future.')
          return GeneratedObjectsHolder(gobjs)
  
      @noPosargs
      @permittedKwargs({})
      def get_id_method(self, args, kwargs):
          return self.held_object.get_id()
  
  class ExecutableHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class StaticLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class SharedLibraryHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          # Set to True only when called from self.func_shared_lib().
          target.shared_library_only = False
  
  class BothLibrariesHolder(BuildTargetHolder):
      def __init__(self, shared_holder, static_holder, interp):
          # FIXME: This build target always represents the shared library, but
          # that should be configurable.
          super().__init__(shared_holder.held_object, interp)
          self.shared_holder = shared_holder
          self.static_holder = static_holder
          self.methods.update({'get_shared_lib': self.get_shared_lib_method,
                               'get_static_lib': self.get_static_lib_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}, {}: {}>'
          h1 = self.shared_holder.held_object
          h2 = self.static_holder.held_object
          return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename)
  
      @noPosargs
      @permittedKwargs({})
      def get_shared_lib_method(self, args, kwargs):
          return self.shared_holder
  
      @noPosargs
      @permittedKwargs({})
      def get_static_lib_method(self, args, kwargs):
          return self.static_holder
  
  class SharedModuleHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class JarHolder(BuildTargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
  
  class CustomTargetIndexHolder(InterpreterObject, ObjectHolder):
      def __init__(self, object_to_hold):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, object_to_hold)
  
  class CustomTargetHolder(TargetHolder):
      def __init__(self, target, interp):
          super().__init__(target, interp)
          self.methods.update({'full_path': self.full_path_method,
                               })
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
      @noPosargs
      @permittedKwargs({})
      def full_path_method(self, args, kwargs):
          return self.interpreter.backend.get_target_filename_abs(self.held_object)
  
      def __getitem__(self, index):
          return CustomTargetIndexHolder(self.held_object[index])
  
      def __setitem__(self, index, value):
          raise InterpreterException('Cannot set a member of a CustomTarget')
  
      def __delitem__(self, index):
          raise InterpreterException('Cannot delete a member of a CustomTarget')
  
  class RunTargetHolder(InterpreterObject, ObjectHolder):
      def __init__(self, name, command, args, dependencies, subdir, subproject):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.RunTarget(name, command, args, dependencies, subdir, subproject))
  
      def __repr__(self):
          r = '<{} {}: {}>'
          h = self.held_object
          return r.format(self.__class__.__name__, h.get_id(), h.command)
  
  class Test(InterpreterObject):
      def __init__(self, name, project, suite, exe, depends, is_parallel,
                   cmd_args, env, should_fail, timeout, workdir):
          InterpreterObject.__init__(self)
          self.name = name
          self.suite = suite
          self.project_name = project
          self.exe = exe
          self.depends = depends
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
  
      def get_exe(self):
          return self.exe
  
      def get_name(self):
          return self.name
  
  class SubprojectHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subinterpreter):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, subinterpreter)
          self.methods.update({'get_variable': self.get_variable_method,
                               })
  
      @permittedKwargs({})
      def get_variable_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Get_variable takes one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Get_variable takes a string argument.')
          if varname not in self.held_object.variables:
              raise InvalidArguments('Requested variable "{0}" not found.'.format(varname))
          return self.held_object.variables[varname]
  
  class CompilerHolder(InterpreterObject):
      def __init__(self, compiler, env):
          InterpreterObject.__init__(self)
          self.compiler = compiler
          self.environment = env
          self.methods.update({'compiles': self.compiles_method,
                               'links': self.links_method,
                               'get_id': self.get_id_method,
                               'compute_int': self.compute_int_method,
                               'sizeof': self.sizeof_method,
                               'get_define': self.get_define_method,
                               'check_header': self.check_header_method,
                               'has_header': self.has_header_method,
                               'has_header_symbol': self.has_header_symbol_method,
                               'run': self.run_method,
                               'has_function': self.has_function_method,
                               'has_member': self.has_member_method,
                               'has_members': self.has_members_method,
                               'has_type': self.has_type_method,
                               'alignment': self.alignment_method,
                               'version': self.version_method,
                               'cmd_array': self.cmd_array_method,
                               'find_library': self.find_library_method,
                               'has_argument': self.has_argument_method,
                               'has_multi_arguments': self.has_multi_arguments_method,
                               'get_supported_arguments': self.get_supported_arguments_method,
                               'first_supported_argument': self.first_supported_argument_method,
                               'has_link_argument': self.has_link_argument_method,
                               'has_multi_link_arguments': self.has_multi_link_arguments_method,
                               'get_supported_link_arguments': self.get_supported_link_arguments_method,
                               'first_supported_link_argument': self.first_supported_link_argument_method,
                               'unittest_args': self.unittest_args_method,
                               'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def version_method(self, args, kwargs):
          return self.compiler.version
  
      @noPosargs
      @permittedKwargs({})
      def cmd_array_method(self, args, kwargs):
          return self.compiler.exelist
  
      def determine_args(self, kwargs):
          nobuiltins = kwargs.get('no_builtin_args', False)
          if not isinstance(nobuiltins, bool):
              raise InterpreterException('Type of no_builtin_args not a boolean.')
          args = []
          incdirs = extract_as_list(kwargs, 'include_directories')
          for i in incdirs:
              if not isinstance(i, IncludeDirsHolder):
                  raise InterpreterException
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/interpreter.py
files.new_contents: # Copyright 2013-2014 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.
  
  import sys, pickle, os, shutil, subprocess, gzip, platform, errno
  import shlex
  from glob import glob
  from . import depfixer
  from . import destdir_join
  from ..mesonlib import is_windows, Popen_safe
  from __main__ import __file__ as main_file
  
  install_log_file = None
  selinux_updates = []
  
  class DirMaker:
      def __init__(self):
          self.dirs = []
  
      def makedirs(self, path, exist_ok=False):
          dirname = os.path.normpath(path)
          dirs = []
          while dirname != os.path.dirname(dirname):
              if not os.path.exists(dirname):
                  dirs.append(dirname)
              dirname = os.path.dirname(dirname)
          os.makedirs(path, exist_ok=exist_ok)
  
          # store the directories in creation order, with the parent directory
          # before the child directories. Future calls of makedir() will not
          # create the parent directories, so the last element in the list is
          # the last one to be created. That is the first one to be removed on
          # __exit__
          dirs.reverse()
          self.dirs += dirs
  
      def __enter__(self):
          return self
  
      def __exit__(self, type, value, traceback):
          self.dirs.reverse()
          for d in self.dirs:
              append_to_log(d)
  
  def is_executable(path):
      '''Checks whether any of the "x" bits are set in the source file mode.'''
      return bool(os.stat(path).st_mode & 0o111)
  
  def sanitize_permissions(path, umask):
      if umask is None:
          return
      new_perms = 0o777 if is_executable(path) else 0o666
      new_perms &= ~umask
      try:
          os.chmod(path, new_perms)
      except PermissionError as e:
          msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
          print(msg.format(path, new_perms, e.strerror))
  
  def set_mode(path, mode, default_umask):
      if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
          # Just sanitize permissions with the default umask
          sanitize_permissions(path, default_umask)
          return
      # No chown() on Windows, and must set one of owner/group
      if not is_windows() and (mode.owner or mode.group) is not None:
          try:
              shutil.chown(path, mode.owner, mode.group)
          except PermissionError as e:
              msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...'
              print(msg.format(path, mode.owner, mode.group, e.strerror))
          except LookupError:
              msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...'
              print(msg.format(path, mode.owner, mode.group))
          except OSError as e:
              if e.errno == errno.EINVAL:
                  msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...'
                  print(msg.format(path, mode.owner, mode.group))
              else:
                  raise
      # Must set permissions *after* setting owner/group otherwise the
      # setuid/setgid bits will get wiped by chmod
      # NOTE: On Windows you can set read/write perms; the rest are ignored
      if mode.perms_s is not None:
          try:
              os.chmod(path, mode.perms)
          except PermissionError as e:
              msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
              print(msg.format(path, mode.perms_s, e.strerror))
      else:
          sanitize_permissions(path, default_umask)
  
  def restore_selinux_contexts():
      '''
      Restores the SELinux context for files in @selinux_updates
  
      If $DESTDIR is set, do not warn if the call fails.
      '''
      try:
          subprocess.check_call(['selinuxenabled'])
      except (FileNotFoundError, PermissionError, subprocess.CalledProcessError) as e:
          # If we don't have selinux or selinuxenabled returned 1, failure
          # is ignored quietly.
          return
  
      if not shutil.which('restorecon'):
          # If we don't have restorecon, failure is ignored quietly.
          return
  
      with subprocess.Popen(['restorecon', '-F', '-f-', '-0'],
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
          out, err = proc.communicate(input=b'\0'.join(os.fsencode(f) for f in selinux_updates) + b'\0')
          if proc.returncode != 0 and not os.environ.get('DESTDIR'):
              print('Failed to restore SELinux context of installed files...',
                    'Standard output:', out.decode(),
                    'Standard error:', err.decode(), sep='\n')
  
  def append_to_log(line):
      install_log_file.write(line)
      if not line.endswith('\n'):
          install_log_file.write('\n')
      install_log_file.flush()
  
  def do_copyfile(from_file, to_file):
      if not os.path.isfile(from_file):
          raise RuntimeError('Tried to install something that isn\'t a file:'
                             '{!r}'.format(from_file))
      # copyfile fails if the target file already exists, so remove it to
      # allow overwriting a previous install. If the target is not a file, we
      # want to give a readable error.
      if os.path.exists(to_file):
          if not os.path.isfile(to_file):
              raise RuntimeError('Destination {!r} already exists and is not '
                                 'a file'.format(to_file))
          os.unlink(to_file)
      shutil.copyfile(from_file, to_file)
      shutil.copystat(from_file, to_file)
      selinux_updates.append(to_file)
      append_to_log(to_file)
  
  def do_copydir(data, src_dir, dst_dir, exclude, install_mode):
      '''
      Copies the contents of directory @src_dir into @dst_dir.
  
      For directory
          /foo/
            bar/
              excluded
              foobar
            file
      do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}, None) creates
          /dst/
            dir/
              bar/
                foobar
              file
  
      Args:
          src_dir: str, absolute path to the source directory
          dst_dir: str, absolute path to the destination directory
          exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs),
                   each element of the set is a path relative to src_dir.
          install_mode: FileMode object, or None to use defaults.
      '''
      if not os.path.isabs(src_dir):
          raise ValueError('src_dir must be absolute, got %s' % src_dir)
      if not os.path.isabs(dst_dir):
          raise ValueError('dst_dir must be absolute, got %s' % dst_dir)
      if exclude is not None:
          exclude_files, exclude_dirs = exclude
      else:
          exclude_files = exclude_dirs = set()
      for root, dirs, files in os.walk(src_dir):
          assert os.path.isabs(root)
          for d in dirs[:]:
              abs_src = os.path.join(root, d)
              filepart = os.path.relpath(abs_src, start=src_dir)
              abs_dst = os.path.join(dst_dir, filepart)
              # Remove these so they aren't visited by os.walk at all.
              if filepart in exclude_dirs:
                  dirs.remove(d)
                  continue
              if os.path.isdir(abs_dst):
                  continue
              if os.path.exists(abs_dst):
                  print('Tried to copy directory %s but a file of that name already exists.' % abs_dst)
                  sys.exit(1)
              data.dirmaker.makedirs(abs_dst)
              shutil.copystat(abs_src, abs_dst)
              sanitize_permissions(abs_dst, data.install_umask)
          for f in files:
              abs_src = os.path.join(root, f)
              filepart = os.path.relpath(abs_src, start=src_dir)
              if filepart in exclude_files:
                  continue
              abs_dst = os.path.join(dst_dir, filepart)
              if os.path.isdir(abs_dst):
                  print('Tried to copy file %s but a directory of that name already exists.' % abs_dst)
              if os.path.exists(abs_dst):
                  os.unlink(abs_dst)
              parent_dir = os.path.dirname(abs_dst)
              if not os.path.isdir(parent_dir):
                  os.mkdir(parent_dir)
                  shutil.copystat(os.path.dirname(abs_src), parent_dir)
              shutil.copy2(abs_src, abs_dst, follow_symlinks=False)
              set_mode(abs_dst, install_mode, data.install_umask)
              append_to_log(abs_dst)
  
  def get_destdir_path(d, path):
      if os.path.isabs(path):
          output = destdir_join(d.destdir, path)
      else:
          output = os.path.join(d.fullprefix, path)
      return output
  
  def do_install(log_dir, datafilename):
      global install_log_file
  
      with open(datafilename, 'rb') as ifile:
          d = pickle.load(ifile)
      d.destdir = os.environ.get('DESTDIR', '')
      d.fullprefix = destdir_join(d.destdir, d.prefix)
  
      if d.install_umask is not None:
          os.umask(d.install_umask)
  
      with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf:
          install_log_file = lf
          append_to_log('# List of files installed by Meson')
          append_to_log('# Does not contain files installed by custom scripts.')
  
          try:
              d.dirmaker = DirMaker()
              with d.dirmaker:
                  install_subdirs(d) # Must be first, because it needs to delete the old subtree.
                  install_targets(d)
                  install_headers(d)
                  install_man(d)
                  install_data(d)
                  restore_selinux_contexts()
                  run_install_script(d)
          except PermissionError:
              if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ:
                  print('Installation failed due to insufficient permissions.')
                  print('Attempting to use polkit to gain elevated privileges...')
                  os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
                            os.getcwd())
              else:
                  raise
  
  
  def install_subdirs(d):
      for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
          full_dst_dir = get_destdir_path(d, dst_dir)
          print('Installing subdir %s to %s' % (src_dir, full_dst_dir))
          d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
          do_copydir(d, src_dir, full_dst_dir, exclude, mode)
  
  def install_data(d):
      for i in d.data:
          fullfilename = i[0]
          outfilename = get_destdir_path(d, i[1])
          mode = i[2]
          outdir = os.path.dirname(outfilename)
          d.dirmaker.makedirs(outdir, exist_ok=True)
          print('Installing %s to %s' % (fullfilename, outdir))
          do_copyfile(fullfilename, outfilename)
          set_mode(outfilename, mode, d.install_umask)
  
  def install_man(d):
      for m in d.man:
          full_source_filename = m[0]
          outfilename = get_destdir_path(d, m[1])
          outdir = os.path.dirname(outfilename)
          d.dirmaker.makedirs(outdir, exist_ok=True)
          install_mode = m[2]
          print('Installing %s to %s' % (full_source_filename, outdir))
          if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'):
              with open(outfilename, 'wb') as of:
                  with open(full_source_filename, 'rb') as sf:
                      # Set mtime and filename for reproducibility.
                      with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz:
                          gz.write(sf.read())
              shutil.copystat(full_source_filename, outfilename)
              append_to_log(outfilename)
          else:
              do_copyfile(full_source_filename, outfilename)
          set_mode(outfilename, install_mode, d.install_umask)
  
  def install_headers(d):
      for t in d.headers:
          fullfilename = t[0]
          fname = os.path.basename(fullfilename)
          outdir = get_destdir_path(d, t[1])
          outfilename = os.path.join(outdir, fname)
          install_mode = t[2]
          print('Installing %s to %s' % (fname, outdir))
          d.dirmaker.makedirs(outdir, exist_ok=True)
          do_copyfile(fullfilename, outfilename)
          set_mode(outfilename, install_mode, d.install_umask)
  
  def run_install_script(d):
      env = {'MESON_SOURCE_ROOT': d.source_dir,
             'MESON_BUILD_ROOT': d.build_dir,
             'MESON_INSTALL_PREFIX': d.prefix,
             'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix,
             'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
             }
  
      child_env = os.environ.copy()
      child_env.update(env)
  
      for i in d.install_scripts:
          script = i['exe']
          args = i['args']
          name = ' '.join(script + args)
          print('Running custom install script {!r}'.format(name))
          try:
              rc = subprocess.call(script + args, env=child_env)
              if rc != 0:
                  sys.exit(rc)
          except OSError:
              print('Failed to run install script {!r}'.format(name))
              sys.exit(1)
  
  def check_for_stampfile(fname):
      '''Some languages e.g. Rust have output files
      whose names are not known at configure time.
      Check if this is the case and return the real
      file instead.'''
      if fname.endswith('.so') or fname.endswith('.dll'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + suffix)
              if len(files) > 1:
                  print("Stale dynamic library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      elif fname.endswith('.a') or fname.endswith('.lib'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + '.rlib')
              if len(files) > 1:
                  print("Stale static library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      return fname
  
  def install_targets(d):
      for t in d.targets:
          fname = check_for_stampfile(t[0])
          outdir = get_destdir_path(d, t[1])
          outname = os.path.join(outdir, os.path.basename(fname))
          aliases = t[2]
          should_strip = t[3]
          install_rpath = t[4]
          install_mode = t[5]
          print('Installing %s to %s' % (fname, outname))
          d.dirmaker.makedirs(outdir, exist_ok=True)
          if not os.path.exists(fname):
              raise RuntimeError('File {!r} could not be found'.format(fname))
          elif os.path.isfile(fname):
              do_copyfile(fname, outname)
              set_mode(outname, install_mode, d.install_umask)
              if should_strip and d.strip_bin is not None:
                  if fname.endswith('.jar'):
                      print('Not stripping jar target:', os.path.basename(fname))
                      continue
                  print('Stripping target {!r}'.format(fname))
                  ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
                  if ps.returncode != 0:
                      print('Could not strip file.\n')
                      print('Stdout:\n%s\n' % stdo)
                      print('Stderr:\n%s\n' % stde)
                      sys.exit(1)
              pdb_filename = os.path.splitext(fname)[0] + '.pdb'
              if not should_strip and os.path.exists(pdb_filename):
                  pdb_outname = os.path.splitext(outname)[0] + '.pdb'
                  print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname))
                  do_copyfile(pdb_filename, pdb_outname)
                  set_mode(pdb_outname, install_mode, d.install_umask)
          elif os.path.isdir(fname):
              fname = os.path.join(d.build_dir, fname.rstrip('/'))
              outname = os.path.join(outdir, os.path.basename(fname))
              do_copydir(d, fname, outname, None, install_mode)
          else:
              raise RuntimeError('Unknown file type for {!r}'.format(fname))
          printed_symlink_error = False
          for alias, to in aliases.items():
              try:
                  symlinkfilename = os.path.join(outdir, alias)
                  try:
                      os.unlink(symlinkfilename)
                  except FileNotFoundError:
                      pass
                  os.symlink(to, symlinkfilename)
                  append_to_log(symlinkfilename)
              except (NotImplementedError, OSError):
                  if not printed_symlink_error:
                      print("Symlink creation does not work on this platform. "
                            "Skipping all symlinking.")
                      printed_symlink_error = True
          if os.path.isfile(outname):
              try:
                  depfixer.fix_rpath(outname, install_rpath, False)
              except SystemExit as e:
                  if isinstance(e.code, int) and e.code == 0:
                      pass
                  else:
                      raise
  
  def run(args):
      if len(args) != 1 and len(args) != 2:
          print('Installer script for Meson. Do not run on your own, mmm\'kay?')
          print('meson_install.py [install info file]')
      datafilename = args[0]
      private_dir = os.path.dirname(datafilename)
      log_dir = os.path.join(private_dir, '../meson-logs')
      if len(args) == 2:
          os.chdir(args[1])
      do_install(log_dir, datafilename)
      install_log_file = None
      return 0
  
  if __name__ == '__main__':
      sys.exit(run(sys.argv[1:]))
  
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/scripts/meson_install.py
files.old_contents: # Copyright 2013-2014 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.
  
  import sys, pickle, os, shutil, subprocess, gzip, platform, errno
  import shlex
  from glob import glob
  from . import depfixer
  from . import destdir_join
  from ..mesonlib import is_windows, Popen_safe
  from __main__ import __file__ as main_file
  
  install_log_file = None
  selinux_updates = []
  
  class DirMaker:
      def __init__(self):
          self.dirs = []
  
      def makedirs(self, path, exist_ok=False):
          dirname = os.path.normpath(path)
          dirs = []
          while dirname != os.path.dirname(dirname):
              if not os.path.exists(dirname):
                  dirs.append(dirname)
              dirname = os.path.dirname(dirname)
          os.makedirs(path, exist_ok=exist_ok)
  
          # store the directories in creation order, with the parent directory
          # before the child directories. Future calls of makedir() will not
          # create the parent directories, so the last element in the list is
          # the last one to be created. That is the first one to be removed on
          # __exit__
          dirs.reverse()
          self.dirs += dirs
  
      def __enter__(self):
          return self
  
      def __exit__(self, type, value, traceback):
          self.dirs.reverse()
          for d in self.dirs:
              append_to_log(d)
  
  def is_executable(path):
      '''Checks whether any of the "x" bits are set in the source file mode.'''
      return bool(os.stat(path).st_mode & 0o111)
  
  def sanitize_permissions(path, umask):
      if umask is None:
          return
      new_perms = 0o777 if is_executable(path) else 0o666
      new_perms &= ~umask
      try:
          os.chmod(path, new_perms)
      except PermissionError as e:
          msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
          print(msg.format(path, new_perms, e.strerror))
  
  def set_mode(path, mode, default_umask):
      if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
          # Just sanitize permissions with the default umask
          sanitize_permissions(path, default_umask)
          return
      # No chown() on Windows, and must set one of owner/group
      if not is_windows() and (mode.owner or mode.group) is not None:
          try:
              shutil.chown(path, mode.owner, mode.group)
          except PermissionError as e:
              msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...'
              print(msg.format(path, mode.owner, mode.group, e.strerror))
          except LookupError:
              msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...'
              print(msg.format(path, mode.owner, mode.group))
          except OSError as e:
              if e.errno == errno.EINVAL:
                  msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...'
                  print(msg.format(path, mode.owner, mode.group))
              else:
                  raise
      # Must set permissions *after* setting owner/group otherwise the
      # setuid/setgid bits will get wiped by chmod
      # NOTE: On Windows you can set read/write perms; the rest are ignored
      if mode.perms_s is not None:
          try:
              os.chmod(path, mode.perms)
          except PermissionError as e:
              msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
              print(msg.format(path, mode.perms_s, e.strerror))
      else:
          sanitize_permissions(path, default_umask)
  
  def restore_selinux_contexts():
      '''
      Restores the SELinux context for files in @selinux_updates
  
      If $DESTDIR is set, do not warn if the call fails.
      '''
      try:
          subprocess.check_call(['selinuxenabled'])
      except (FileNotFoundError, PermissionError, subprocess.CalledProcessError) as e:
          # If we don't have selinux or selinuxenabled returned 1, failure
          # is ignored quietly.
          return
  
      if not shutil.which('restorecon'):
          # If we don't have restorecon, failure is ignored quietly.
          return
  
      with subprocess.Popen(['restorecon', '-F', '-f-', '-0'],
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
          out, err = proc.communicate(input=b'\0'.join(os.fsencode(f) for f in selinux_updates) + b'\0')
          if proc.returncode != 0 and not os.environ.get('DESTDIR'):
              print('Failed to restore SELinux context of installed files...',
                    'Standard output:', out.decode(),
                    'Standard error:', err.decode(), sep='\n')
  
  def append_to_log(line):
      install_log_file.write(line)
      if not line.endswith('\n'):
          install_log_file.write('\n')
      install_log_file.flush()
  
  def do_copyfile(from_file, to_file):
      if not os.path.isfile(from_file):
          raise RuntimeError('Tried to install something that isn\'t a file:'
                             '{!r}'.format(from_file))
      # copyfile fails if the target file already exists, so remove it to
      # allow overwriting a previous install. If the target is not a file, we
      # want to give a readable error.
      if os.path.exists(to_file):
          if not os.path.isfile(to_file):
              raise RuntimeError('Destination {!r} already exists and is not '
                                 'a file'.format(to_file))
          os.unlink(to_file)
      shutil.copyfile(from_file, to_file)
      shutil.copystat(from_file, to_file)
      selinux_updates.append(to_file)
      append_to_log(to_file)
  
  def do_copydir(data, src_dir, dst_dir, exclude):
      '''
      Copies the contents of directory @src_dir into @dst_dir.
  
      For directory
          /foo/
            bar/
              excluded
              foobar
            file
      do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates
          /dst/
            dir/
              bar/
                foobar
              file
  
      Args:
          src_dir: str, absolute path to the source directory
          dst_dir: str, absolute path to the destination directory
          exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs),
                   each element of the set is a path relative to src_dir.
      '''
      if not os.path.isabs(src_dir):
          raise ValueError('src_dir must be absolute, got %s' % src_dir)
      if not os.path.isabs(dst_dir):
          raise ValueError('dst_dir must be absolute, got %s' % dst_dir)
      if exclude is not None:
          exclude_files, exclude_dirs = exclude
      else:
          exclude_files = exclude_dirs = set()
      for root, dirs, files in os.walk(src_dir):
          assert os.path.isabs(root)
          for d in dirs[:]:
              abs_src = os.path.join(root, d)
              filepart = os.path.relpath(abs_src, start=src_dir)
              abs_dst = os.path.join(dst_dir, filepart)
              # Remove these so they aren't visited by os.walk at all.
              if filepart in exclude_dirs:
                  dirs.remove(d)
                  continue
              if os.path.isdir(abs_dst):
                  continue
              if os.path.exists(abs_dst):
                  print('Tried to copy directory %s but a file of that name already exists.' % abs_dst)
                  sys.exit(1)
              data.dirmaker.makedirs(abs_dst)
              shutil.copystat(abs_src, abs_dst)
              sanitize_permissions(abs_dst, data.install_umask)
          for f in files:
              abs_src = os.path.join(root, f)
              filepart = os.path.relpath(abs_src, start=src_dir)
              if filepart in exclude_files:
                  continue
              abs_dst = os.path.join(dst_dir, filepart)
              if os.path.isdir(abs_dst):
                  print('Tried to copy file %s but a directory of that name already exists.' % abs_dst)
              if os.path.exists(abs_dst):
                  os.unlink(abs_dst)
              parent_dir = os.path.dirname(abs_dst)
              if not os.path.isdir(parent_dir):
                  os.mkdir(parent_dir)
                  shutil.copystat(os.path.dirname(abs_src), parent_dir)
              shutil.copy2(abs_src, abs_dst, follow_symlinks=False)
              sanitize_permissions(abs_dst, data.install_umask)
              append_to_log(abs_dst)
  
  def get_destdir_path(d, path):
      if os.path.isabs(path):
          output = destdir_join(d.destdir, path)
      else:
          output = os.path.join(d.fullprefix, path)
      return output
  
  def do_install(log_dir, datafilename):
      global install_log_file
  
      with open(datafilename, 'rb') as ifile:
          d = pickle.load(ifile)
      d.destdir = os.environ.get('DESTDIR', '')
      d.fullprefix = destdir_join(d.destdir, d.prefix)
  
      if d.install_umask is not None:
          os.umask(d.install_umask)
  
      with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf:
          install_log_file = lf
          append_to_log('# List of files installed by Meson')
          append_to_log('# Does not contain files installed by custom scripts.')
  
          try:
              d.dirmaker = DirMaker()
              with d.dirmaker:
                  install_subdirs(d) # Must be first, because it needs to delete the old subtree.
                  install_targets(d)
                  install_headers(d)
                  install_man(d)
                  install_data(d)
                  restore_selinux_contexts()
                  run_install_script(d)
          except PermissionError:
              if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ:
                  print('Installation failed due to insufficient permissions.')
                  print('Attempting to use polkit to gain elevated privileges...')
                  os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
                            os.getcwd())
              else:
                  raise
  
  
  def install_subdirs(d):
      for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
          full_dst_dir = get_destdir_path(d, dst_dir)
          print('Installing subdir %s to %s' % (src_dir, full_dst_dir))
          d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
          do_copydir(d, src_dir, full_dst_dir, exclude)
          set_mode(full_dst_dir, mode, d.install_umask)
  
  def install_data(d):
      for i in d.data:
          fullfilename = i[0]
          outfilename = get_destdir_path(d, i[1])
          mode = i[2]
          outdir = os.path.dirname(outfilename)
          d.dirmaker.makedirs(outdir, exist_ok=True)
          print('Installing %s to %s' % (fullfilename, outdir))
          do_copyfile(fullfilename, outfilename)
          set_mode(outfilename, mode, d.install_umask)
  
  def install_man(d):
      for m in d.man:
          full_source_filename = m[0]
          outfilename = get_destdir_path(d, m[1])
          outdir = os.path.dirname(outfilename)
          d.dirmaker.makedirs(outdir, exist_ok=True)
          print('Installing %s to %s' % (full_source_filename, outdir))
          if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'):
              with open(outfilename, 'wb') as of:
                  with open(full_source_filename, 'rb') as sf:
                      # Set mtime and filename for reproducibility.
                      with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz:
                          gz.write(sf.read())
              shutil.copystat(full_source_filename, outfilename)
              append_to_log(outfilename)
          else:
              do_copyfile(full_source_filename, outfilename)
          sanitize_permissions(outfilename, d.install_umask)
  
  def install_headers(d):
      for t in d.headers:
          fullfilename = t[0]
          fname = os.path.basename(fullfilename)
          outdir = get_destdir_path(d, t[1])
          outfilename = os.path.join(outdir, fname)
          print('Installing %s to %s' % (fname, outdir))
          d.dirmaker.makedirs(outdir, exist_ok=True)
          do_copyfile(fullfilename, outfilename)
          sanitize_permissions(outfilename, d.install_umask)
  
  def run_install_script(d):
      env = {'MESON_SOURCE_ROOT': d.source_dir,
             'MESON_BUILD_ROOT': d.build_dir,
             'MESON_INSTALL_PREFIX': d.prefix,
             'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix,
             'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
             }
  
      child_env = os.environ.copy()
      child_env.update(env)
  
      for i in d.install_scripts:
          script = i['exe']
          args = i['args']
          name = ' '.join(script + args)
          print('Running custom install script {!r}'.format(name))
          try:
              rc = subprocess.call(script + args, env=child_env)
              if rc != 0:
                  sys.exit(rc)
          except OSError:
              print('Failed to run install script {!r}'.format(name))
              sys.exit(1)
  
  def check_for_stampfile(fname):
      '''Some languages e.g. Rust have output files
      whose names are not known at configure time.
      Check if this is the case and return the real
      file instead.'''
      if fname.endswith('.so') or fname.endswith('.dll'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + suffix)
              if len(files) > 1:
                  print("Stale dynamic library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      elif fname.endswith('.a') or fname.endswith('.lib'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + '.rlib')
              if len(files) > 1:
                  print("Stale static library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      return fname
  
  def install_targets(d):
      for t in d.targets:
          fname = check_for_stampfile(t[0])
          outdir = get_destdir_path(d, t[1])
          outname = os.path.join(outdir, os.path.basename(fname))
          aliases = t[2]
          should_strip = t[3]
          install_rpath = t[4]
          print('Installing %s to %s' % (fname, outname))
          d.dirmaker.makedirs(outdir, exist_ok=True)
          if not os.path.exists(fname):
              raise RuntimeError('File {!r} could not be found'.format(fname))
          elif os.path.isfile(fname):
              do_copyfile(fname, outname)
              sanitize_permissions(outname, d.install_umask)
              if should_strip and d.strip_bin is not None:
                  if fname.endswith('.jar'):
                      print('Not stripping jar target:', os.path.basename(fname))
                      continue
                  print('Stripping target {!r}'.format(fname))
                  ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
                  if ps.returncode != 0:
                      print('Could not strip file.\n')
                      print('Stdout:\n%s\n' % stdo)
                      print('Stderr:\n%s\n' % stde)
                      sys.exit(1)
              pdb_filename = os.path.splitext(fname)[0] + '.pdb'
              if not should_strip and os.path.exists(pdb_filename):
                  pdb_outname = os.path.splitext(outname)[0] + '.pdb'
                  print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname))
                  do_copyfile(pdb_filename, pdb_outname)
                  sanitize_permissions(pdb_outname, d.install_umask)
          elif os.path.isdir(fname):
              fname = os.path.join(d.build_dir, fname.rstrip('/'))
              outname = os.path.join(outdir, os.path.basename(fname))
              do_copydir(d, fname, outname, None)
              sanitize_permissions(outname, d.install_umask)
          else:
              raise RuntimeError('Unknown file type for {!r}'.format(fname))
          printed_symlink_error = False
          for alias, to in aliases.items():
              try:
                  symlinkfilename = os.path.join(outdir, alias)
                  try:
                      os.unlink(symlinkfilename)
                  except FileNotFoundError:
                      pass
                  os.symlink(to, symlinkfilename)
                  append_to_log(symlinkfilename)
              except (NotImplementedError, OSError):
                  if not printed_symlink_error:
                      print("Symlink creation does not work on this platform. "
                            "Skipping all symlinking.")
                      printed_symlink_error = True
          if os.path.isfile(outname):
              try:
                  depfixer.fix_rpath(outname, install_rpath, False)
              except SystemExit as e:
                  if isinstance(e.code, int) and e.code == 0:
                      pass
                  else:
                      raise
  
  def run(args):
      if len(args) != 1 and len(args) != 2:
          print('Installer script for Meson. Do not run on your own, mmm\'kay?')
          print('meson_install.py [install info file]')
      datafilename = args[0]
      private_dir = os.path.dirname(datafilename)
      log_dir = os.path.join(private_dir, '../meson-logs')
      if len(args) == 2:
          os.chdir(args[1])
      do_install(log_dir, datafilename)
      install_log_file = None
      return 0
  
  if __name__ == '__main__':
      sys.exit(run(sys.argv[1:]))
  
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/scripts/meson_install.py