files: # 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
  
  [... the rest is omitted ...]
files: mesonbuild/backend/ninjabackend.py
files: # 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
  
  [... the rest is omitted ...]
files: mesonbuild/backend/ninjabackend.py
files: # 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):
                 
  
  [... the rest is omitted ...]
files: mesonbuild/build.py
files: # 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
  
  [... the rest is omitted ...]
files: mesonbuild/build.py
files: # 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,
  
  [... the rest is omitted ...]
files: mesonbuild/interpreter.py
files: # 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,
  
  [... the rest is omitted ...]
files: mesonbuild/interpreter.py
files: # 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.s
  
  [... the rest is omitted ...]
files: mesonbuild/scripts/meson_install.py
files: # 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
  
  [... the rest is omitted ...]
files: mesonbuild/scripts/meson_install.py