files.new_contents: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import copy, os, re
  from collections import OrderedDict
  import itertools, pathlib
  import pickle
  
  from . import environment
  from . import dependencies
  from . import mlog
  from .mesonlib import File, MesonException, listify, extract_as_list
  from .mesonlib import typeslistify, stringlistify, classify_unity_sources
  from .mesonlib import get_filenames_templates_dict, substitute_values
  from .mesonlib import for_windows, for_darwin, for_cygwin, for_android, has_path_sep
  from .compilers import is_object, clink_langs, sort_clink, lang_suffixes
  from .interpreterbase import FeatureNew
  
  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_stale = 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
  
      def process_kwargs(self, kwargs):
          if 'build_by_default' in kwargs:
              self.build_by_default = kwargs['build_by_default']
              if not isinstance(self.build_by_default, bool):
                  raise InvalidArguments('build_by_default must be a boolean value.')
          self.option_overrides = self.parse_overrides(kwargs)
  
      def parse_overrides(self, kwargs):
          result = {}
          overrides = stringlistify(kwargs.get('override_options', []))
          for o in overrides:
              if '=' not in o:
                  raise InvalidArguments('Overrides must be of form "key=value"')
              k, v = o.split('=', 1)
              k = k.strip()
              v = v.strip()
              result[k] = v
          return result
  
  
  class BuildTarget(Target):
      known_kwargs = known_build_target_kwargs
  
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, True)
          self.is_cross = is_cross
          unity_opt = environment.coredata.get_builtin_option('unity')
          self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
          self.environment = environment
          self.sources = []
          self.compilers = OrderedDict()
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_whole_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The list of all files outputted by this target. Useful in cases such
          # as Vala which generates .vapi and .h besides the compiled output.
          self.outputs = [self.filename]
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          self.d_features = {}
          # Sources can be:
          # 1. Pre-existing source files in the source tree
          # 2. Pre-existing sources generated by configure_file in the build tree
          # 3. Sources files generated by another target or a Generator
          self.process_sourcelist(sources)
          # Objects can be:
          # 1. Pre-existing objects provided by the user with the `objects:` kwarg
          # 2. Compiled objects created by and extracted from another target
          self.process_objectlist(objects)
          self.process_kwargs(kwargs, environment)
          self.check_unknown_kwargs(kwargs)
          self.process_compilers()
          if not any([self.sources, self.generated, self.objects, self.link_whole]):
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers_late()
          self.validate_sources()
          self.validate_cross_install(environment)
          self.check_module_linking()
  
      def __lt__(self, other):
          return self.get_id() < other.get_id()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      def validate_cross_install(self, environment):
          if environment.is_cross_build() and not self.is_cross and self.install:
              raise InvalidArguments('Tried to install a natively built target in a cross build.')
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, self.known_kwargs)
  
      def check_unknown_kwargs_int(self, kwargs, known_kwargs):
          unknowns = []
          for k in kwargs:
              if k not in known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword argument(s) in target %s: %s.' %
                           (self.name, ', '.join(unknowns)))
  
      def process_objectlist(self, objects):
          assert(isinstance(objects, list))
          for s in objects:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, (str, File, ExtractedObjects)):
                  self.objects.append(s)
              elif isinstance(s, (GeneratedList, CustomTarget)):
                  msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
                      'for target {!r}.\nIt is meant only for '.format(self.name) + \
                      'pre-built object files that are shipped with the\nsource ' + \
                      'tree. Try adding it in the list of sources.'
                  raise InvalidArguments(msg)
              else:
                  msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      def process_sourcelist(self, sources):
          sources = listify(sources)
          added_sources = {} # If the same source is defined multiple times, use it only once.
          for s in sources:
              # Holder unpacking. Ugly.
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, File):
                  if s not in added_sources:
                      self.sources.append(s)
                      added_sources[s] = True
              elif isinstance(s, (GeneratedList, CustomTarget, CustomTargetIndex)):
                  self.generated.append(s)
              else:
                  msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      @staticmethod
      def can_compile_remove_sources(compiler, sources):
          removed = False
          for s in sources[:]:
              if compiler.can_compile(s):
                  sources.remove(s)
                  removed = True
          return removed
  
      def process_compilers_late(self):
          """Processes additional compilers after kwargs have been evaluated.
  
          This can add extra compilers that might be required by keyword
          arguments, such as link_with or dependencies. It will also try to guess
          which compiler to use if one hasn't been selected already.
          """
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
  
          # If this library is linked against another library we need to consider
          # the languages of those libraries as well.
          if self.link_targets or self.link_whole_targets:
              extra = set()
              for t in itertools.chain(self.link_targets, self.link_whole_targets):
                  for name, compiler in t.compilers.items():
                      if name in clink_langs:
                          extra.add((name, compiler))
              for name, compiler in sorted(extra, key=lambda p: sort_clink(p[0])):
                  self.compilers[name] = compiler
  
          if not self.compilers:
              # No source files or parent targets, target consists of only object
              # files of unknown origin. Just add the first clink compiler
              # that we have and hope that it can link these objects
              for lang in clink_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
  
      def process_compilers(self):
          '''
          Populate self.compilers, which is the list of compilers that this
          target will use for compiling all its sources.
          We also add compilers that were used by extracted objects to simplify
          dynamic linker determination.
          '''
          if not self.sources and not self.generated and not self.objects:
              return
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
          # Pre-existing sources
          sources = list(self.sources)
          # All generated sources
          for gensrc in self.generated:
              for s in gensrc.get_outputs():
                  # Generated objects can't be compiled, so don't use them for
                  # compiler detection. If our target only has generated objects,
                  # we will fall back to using the first c-like compiler we find,
                  # which is what we need.
                  if not is_object(s):
                      sources.append(s)
          for d in self.external_deps:
              if hasattr(d, 'held_object'):
                  d = d.held_object
              for s in d.sources:
                  if isinstance(s, (str, File)):
                      sources.append(s)
  
          # Sources that were used to create our extracted objects
          for o in self.objects:
              if not isinstance(o, ExtractedObjects):
                  continue
              for s in o.srclist:
                  # Don't add Vala sources since that will pull in the Vala
                  # compiler even though we will never use it since we are
                  # dealing with compiled C code.
                  if not s.endswith(lang_suffixes['vala']):
                      sources.append(s)
          if sources:
              # For each source, try to add one compiler that can compile it.
              # It's ok if no compilers can do so, because users are expected to
              # be able to add arbitrary non-source files to the sources list.
              for s in sources:
                  for lang, compiler in compilers.items():
                      if compiler.can_compile(s):
                          if lang not in self.compilers:
                              self.compilers[lang] = compiler
                          break
              # Re-sort according to clink_langs
              self.compilers = OrderedDict(sorted(self.compilers.items(),
                                                  key=lambda t: sort_clink(t[0])))
  
          # If all our sources are Vala, our target also needs the C compiler but
          # it won't get added above.
          if 'vala' in self.compilers and 'c' not in self.compilers:
              self.compilers['c'] = compilers['c']
  
      def validate_sources(self):
          if not self.sources:
              return
          for lang in ('cs', 'java'):
              if lang in self.compilers:
                  check_sources = list(self.sources)
                  compiler = self.compilers[lang]
                  if not self.can_compile_remove_sources(compiler, check_sources):
                      m = 'No {} sources found in target {!r}'.format(lang, self.name)
                      raise InvalidArguments(m)
                  if check_sources:
                      m = '{0} targets can only contain {0} files:\n'.format(lang.capitalize())
                      m += '\n'.join([repr(c) for c in check_sources])
                      raise InvalidArguments(m)
                  # CSharp and Java targets can't contain any other file types
                  assert(len(self.compilers) == 1)
                  return
  
      def process_link_depends(self, sources, environment):
          """Process the link_depends keyword argument.
  
          This is designed to handle strings, Files, and the output of Custom
          Targets. Notably it doesn't handle generator() returned objects, since
          adding them as a link depends would inherently cause them to be
          generated twice, since the output needs to be passed to the ld_args and
          link_depends.
          """
          sources = listify(sources)
          for s in sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
  
              if isinstance(s, File):
                  self.link_depends.append(s)
              elif isinstance(s, str):
                  self.link_depends.append(
                      File.from_source_file(environment.source_dir, self.subdir, s))
              elif hasattr(s, 'get_outputs'):
                  self.link_depends
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/build.py
files.old_contents: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import copy, os, re
  from collections import OrderedDict
  import itertools, pathlib
  import pickle
  
  from . import environment
  from . import dependencies
  from . import mlog
  from .mesonlib import File, MesonException, listify, extract_as_list
  from .mesonlib import typeslistify, stringlistify, classify_unity_sources
  from .mesonlib import get_filenames_templates_dict, substitute_values
  from .mesonlib import for_windows, for_darwin, for_cygwin, for_android, has_path_sep
  from .compilers import is_object, clink_langs, sort_clink, lang_suffixes
  from .interpreterbase import FeatureNew
  
  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_stale = 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
  
      def process_kwargs(self, kwargs):
          if 'build_by_default' in kwargs:
              self.build_by_default = kwargs['build_by_default']
              if not isinstance(self.build_by_default, bool):
                  raise InvalidArguments('build_by_default must be a boolean value.')
          self.option_overrides = self.parse_overrides(kwargs)
  
      def parse_overrides(self, kwargs):
          result = {}
          overrides = stringlistify(kwargs.get('override_options', []))
          for o in overrides:
              if '=' not in o:
                  raise InvalidArguments('Overrides must be of form "key=value"')
              k, v = o.split('=', 1)
              k = k.strip()
              v = v.strip()
              result[k] = v
          return result
  
  
  class BuildTarget(Target):
      known_kwargs = known_build_target_kwargs
  
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, True)
          self.is_cross = is_cross
          unity_opt = environment.coredata.get_builtin_option('unity')
          self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
          self.environment = environment
          self.sources = []
          self.compilers = OrderedDict()
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_whole_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The list of all files outputted by this target. Useful in cases such
          # as Vala which generates .vapi and .h besides the compiled output.
          self.outputs = [self.filename]
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          self.d_features = {}
          # Sources can be:
          # 1. Pre-existing source files in the source tree
          # 2. Pre-existing sources generated by configure_file in the build tree
          # 3. Sources files generated by another target or a Generator
          self.process_sourcelist(sources)
          # Objects can be:
          # 1. Pre-existing objects provided by the user with the `objects:` kwarg
          # 2. Compiled objects created by and extracted from another target
          self.process_objectlist(objects)
          self.process_kwargs(kwargs, environment)
          self.check_unknown_kwargs(kwargs)
          self.process_compilers()
          if not any([self.sources, self.generated, self.objects, self.link_whole]):
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers_late()
          self.validate_sources()
          self.validate_cross_install(environment)
          self.check_module_linking()
  
      def __lt__(self, other):
          return self.get_id() < other.get_id()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      def validate_cross_install(self, environment):
          if environment.is_cross_build() and not self.is_cross and self.install:
              raise InvalidArguments('Tried to install a natively built target in a cross build.')
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, self.known_kwargs)
  
      def check_unknown_kwargs_int(self, kwargs, known_kwargs):
          unknowns = []
          for k in kwargs:
              if k not in known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword argument(s) in target %s: %s.' %
                           (self.name, ', '.join(unknowns)))
  
      def process_objectlist(self, objects):
          assert(isinstance(objects, list))
          for s in objects:
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, (str, File, ExtractedObjects)):
                  self.objects.append(s)
              elif isinstance(s, (GeneratedList, CustomTarget)):
                  msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
                      'for target {!r}.\nIt is meant only for '.format(self.name) + \
                      'pre-built object files that are shipped with the\nsource ' + \
                      'tree. Try adding it in the list of sources.'
                  raise InvalidArguments(msg)
              else:
                  msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      def process_sourcelist(self, sources):
          sources = listify(sources)
          added_sources = {} # If the same source is defined multiple times, use it only once.
          for s in sources:
              # Holder unpacking. Ugly.
              if hasattr(s, 'held_object'):
                  s = s.held_object
              if isinstance(s, File):
                  if s not in added_sources:
                      self.sources.append(s)
                      added_sources[s] = True
              elif isinstance(s, (GeneratedList, CustomTarget, CustomTargetIndex)):
                  self.generated.append(s)
              else:
                  msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
                  raise InvalidArguments(msg)
  
      @staticmethod
      def can_compile_remove_sources(compiler, sources):
          removed = False
          for s in sources[:]:
              if compiler.can_compile(s):
                  sources.remove(s)
                  removed = True
          return removed
  
      def process_compilers_late(self):
          """Processes additional compilers after kwargs have been evaluated.
  
          This can add extra compilers that might be required by keyword
          arguments, such as link_with or dependencies. It will also try to guess
          which compiler to use if one hasn't been selected already.
          """
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
  
          # If this library is linked against another library we need to consider
          # the languages of those libraries as well.
          if self.link_targets or self.link_whole_targets:
              extra = set()
              for t in itertools.chain(self.link_targets, self.link_whole_targets):
                  for name, compiler in t.compilers.items():
                      if name in clink_langs:
                          extra.add((name, compiler))
              for name, compiler in sorted(extra, key=lambda p: sort_clink(p[0])):
                  self.compilers[name] = compiler
  
          if not self.compilers:
              # No source files or parent targets, target consists of only object
              # files of unknown origin. Just add the first clink compiler
              # that we have and hope that it can link these objects
              for lang in clink_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
  
      def process_compilers(self):
          '''
          Populate self.compilers, which is the list of compilers that this
          target will use for compiling all its sources.
          We also add compilers that were used by extracted objects to simplify
          dynamic linker determination.
          '''
          if not self.sources and not self.generated and not self.objects:
              return
          # Populate list of compilers
          if self.is_cross:
              compilers = self.environment.coredata.cross_compilers
          else:
              compilers = self.environment.coredata.compilers
          # Pre-existing sources
          sources = list(self.sources)
          # All generated sources
          for gensrc in self.generated:
              for s in gensrc.get_outputs():
                  # Generated objects can't be compiled, so don't use them for
                  # compiler detection. If our target only has generated objects,
                  # we will fall back to using the first c-like compiler we find,
                  # which is what we need.
                  if not is_object(s):
                      sources.append(s)
          for d in self.external_deps:
              if hasattr(d, 'held_object'):
                  d = d.held_object
              for s in d.sources:
                  if isinstance(s, (str, File)):
                      sources.append(s)
  
          # Sources that were used to create our extracted objects
          for o in self.objects:
              if not isinstance(o, ExtractedObjects):
                  continue
              for s in o.srclist:
                  # Don't add Vala sources since that will pull in the Vala
                  # compiler even though we will never use it since we are
                  # dealing with compiled C code.
                  if not s.endswith(lang_suffixes['vala']):
                      sources.append(s)
          if sources:
              # For each source, try to add one compiler that can compile it.
              # It's ok if no compilers can do so, because users are expected to
              # be able to add arbitrary non-source files to the sources list.
              for s in sources:
                  for lang, compiler in compilers.items():
                      if compiler.can_compile(s):
                          if lang not in self.compilers:
                              self.compilers[lang] = compiler
                          break
              # Re-sort according to clink_langs
              self.compilers = OrderedDict(sorted(self.compilers.items(),
                                                  key=lambda t: sort_clink(t[0])))
  
          # If all our sources are Vala, our target also needs the C compiler but
          # it won't get added above.
          if 'vala' in self.compilers and 'c' not in self.compilers:
              self.compilers['c'] = compilers['c']
  
      def validate_sources(self):
          if not self.sources:
              return
          for lang in ('cs', 'java'):
              if lang in self.compilers:
                  check_sources = list(self.sources)
                  compiler = self.compilers[lang]
                  if not self.can_compile_remove_sources(compiler, check_sources):
                      m = 'No {} sources found in target {!r}'.format(lang, self.name)
                      raise InvalidArguments(m)
                  if check_sources:
                      m = '{0} targets can only contain {0} files:\n'.format(lang.capitalize())
                      m += '\n'.join([repr(c) for c in check_sources])
                      raise InvalidArguments(m)
                  # CSharp and Java targets can't contain any other file types
                  assert(len(self.compilers) == 1)
                  return
  
      def process_link_depends(self, sources, environment):
          """Process the link_depends keyword argument.
  
          This is designed to handle strings, Files, and the output of Custom
          Targets. Notably it doesn't handle generator() returned objects, since
          adding them as a link depends would inherently cause them to be
          generated twice, since the output needs to be passed to the ld_args and
          link_depends.
          """
          sources = listify(sources)
          for s in sources:
              if hasattr(s, 'held_object'):
                  s = s.held_object
  
              if isinstance(s, File):
                  self.link_depends.append(s)
              elif isinstance(s, str):
                  self.link_depends.append(
                      File.from_source_file(environment.source_dir, self.subdir, s))
              elif hasattr(s, 'get_outputs'):
                  self.link_depends
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/build.py
files.new_contents: # Copyright 2012-2018 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mlog
  import pickle, os, uuid, shlex
  import sys
  from pathlib import PurePath
  from collections import OrderedDict
  from .mesonlib import MesonException
  from .mesonlib import default_libdir, default_libexecdir, default_prefix
  import ast
  import argparse
  
  version = '0.48.0.dev1'
  backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode']
  
  default_yielding = False
  
  class UserOption:
      def __init__(self, name, description, choices, yielding):
          super().__init__()
          self.name = name
          self.choices = choices
          self.description = description
          if yielding is None:
              yielding = default_yielding
          if not isinstance(yielding, bool):
              raise MesonException('Value of "yielding" must be a boolean.')
          self.yielding = yielding
  
      # Check that the input is a valid value and return the
      # "cleaned" or "native" version. For example the Boolean
      # option could take the string "true" and return True.
      def validate_value(self, value):
          raise RuntimeError('Derived option class did not override validate_value.')
  
      def set_value(self, newvalue):
          self.value = self.validate_value(newvalue)
  
  class UserStringOption(UserOption):
      def __init__(self, name, description, value, choices=None, yielding=None):
          super().__init__(name, description, choices, yielding)
          self.set_value(value)
  
      def validate_value(self, value):
          if not isinstance(value, str):
              raise MesonException('Value "%s" for string option "%s" is not a string.' % (str(value), self.name))
          return value
  
  class UserBooleanOption(UserOption):
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, [True, False], yielding)
          self.set_value(value)
  
      def __bool__(self):
          return self.value
  
      def validate_value(self, value):
          if isinstance(value, bool):
              return value
          if value.lower() == 'true':
              return True
          if value.lower() == 'false':
              return False
          raise MesonException('Value %s is not boolean (true or false).' % value)
  
  class UserIntegerOption(UserOption):
      def __init__(self, name, description, min_value, max_value, value, yielding=None):
          super().__init__(name, description, [True, False], yielding)
          self.min_value = min_value
          self.max_value = max_value
          self.set_value(value)
          c = []
          if min_value is not None:
              c.append('>=' + str(min_value))
          if max_value is not None:
              c.append('<=' + str(max_value))
          self.choices = ', '.join(c)
  
      def validate_value(self, value):
          if isinstance(value, str):
              value = self.toint(value)
          if not isinstance(value, int):
              raise MesonException('New value for integer option is not an integer.')
          if self.min_value is not None and value < self.min_value:
              raise MesonException('New value %d is less than minimum value %d.' % (value, self.min_value))
          if self.max_value is not None and value > self.max_value:
              raise MesonException('New value %d is more than maximum value %d.' % (value, self.max_value))
          return value
  
      def toint(self, valuestring):
          try:
              return int(valuestring)
          except ValueError:
              raise MesonException('Value string "%s" is not convertable to an integer.' % valuestring)
  
  class UserUmaskOption(UserIntegerOption):
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, 0, 0o777, value, yielding)
          self.choices = ['preserve', '0000-0777']
  
      def validate_value(self, value):
          if value is None or value == 'preserve':
              return None
          return super().validate_value(value)
  
      def toint(self, valuestring):
          try:
              return int(valuestring, 8)
          except ValueError as e:
              raise MesonException('Invalid mode: {}'.format(e))
  
  class UserComboOption(UserOption):
      def __init__(self, name, description, choices, value, yielding=None):
          super().__init__(name, description, choices, yielding)
          if not isinstance(self.choices, list):
              raise MesonException('Combo choices must be an array.')
          for i in self.choices:
              if not isinstance(i, str):
                  raise MesonException('Combo choice elements must be strings.')
          self.set_value(value)
  
      def validate_value(self, value):
          if value not in self.choices:
              optionsstring = ', '.join(['"%s"' % (item,) for item in self.choices])
              raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (value, self.name, optionsstring))
          return value
  
  class UserArrayOption(UserOption):
      def __init__(self, name, description, value, shlex_split=False, user_input=False, **kwargs):
          super().__init__(name, description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None))
          self.shlex_split = shlex_split
          self.value = self.validate_value(value, user_input=user_input)
  
      def validate_value(self, value, user_input=True):
          # User input is for options defined on the command line (via -D
          # options). Users can put their input in as a comma separated
          # string, but for defining options in meson_options.txt the format
          # should match that of a combo
          if not user_input and isinstance(value, str) and not value.startswith('['):
              raise MesonException('Value does not define an array: ' + value)
  
          if isinstance(value, str):
              if value.startswith('['):
                  newvalue = ast.literal_eval(value)
              elif value == '':
                  newvalue = []
              else:
                  if self.shlex_split:
                      newvalue = shlex.split(value)
                  else:
                      newvalue = [v.strip() for v in value.split(',')]
          elif isinstance(value, list):
              newvalue = value
          else:
              raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue)))
  
          if len(set(newvalue)) != len(newvalue):
              msg = 'Duplicated values in array option "%s" is deprecated. ' \
                    'This will become a hard error in the future.' % (self.name)
              mlog.deprecation(msg)
          for i in newvalue:
              if not isinstance(i, str):
                  raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue)))
          if self.choices:
              bad = [x for x in newvalue if x not in self.choices]
              if bad:
                  raise MesonException('Options "{}" are not in allowed choices: "{}"'.format(
                      ', '.join(bad), ', '.join(self.choices)))
          return newvalue
  
  class UserFeatureOption(UserComboOption):
      static_choices = ['enabled', 'disabled', 'auto']
  
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, self.static_choices, value, yielding)
  
      def is_enabled(self):
          return self.value == 'enabled'
  
      def is_disabled(self):
          return self.value == 'disabled'
  
      def is_auto(self):
          return self.value == 'auto'
  
  # This class contains all data that must persist over multiple
  # invocations of Meson. It is roughly the same thing as
  # cmakecache.
  
  class CoreData:
  
      def __init__(self, options):
          self.lang_guids = {
              'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'cpp': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'test': '3AC096D0-A1C2-E12C-1390-A8335801FDAB',
              'directory': '2150E333-8FDC-42A3-9474-1A3956D46DE8',
          }
          self.test_guid = str(uuid.uuid4()).upper()
          self.regen_guid = str(uuid.uuid4()).upper()
          self.target_guids = {}
          self.version = version
          self.init_builtins()
          self.backend_options = {}
          self.user_options = {}
          self.compiler_options = {}
          self.base_options = {}
          self.external_preprocess_args = {} # CPPFLAGS only
          self.cross_file = self.__load_cross_file(options.cross_file)
          self.wrap_mode = options.wrap_mode
          self.compilers = OrderedDict()
          self.cross_compilers = OrderedDict()
          self.deps = OrderedDict()
          # Only to print a warning if it changes between Meson invocations.
          self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '')
  
      @staticmethod
      def __load_cross_file(filename):
          """Try to load the cross file.
  
          If the filename is None return None. If the filename is an absolute
          (after resolving variables and ~), return that absolute path. Next,
          check if the file is relative to the current source dir. If the path
          still isn't resolved do the following:
              Windows:
                  - Error
              *:
                  - $XDG_DATA_HOME/meson/cross (or ~/.local/share/meson/cross if
                    undefined)
                  - $XDG_DATA_DIRS/meson/cross (or
                    /usr/local/share/meson/cross:/usr/share/meson/cross if undefined)
                  - Error
  
          Non-Windows follows the Linux path and will honor XDG_* if set. This
          simplifies the implementation somewhat.
          """
          if filename is None:
              return None
          filename = os.path.expanduser(os.path.expandvars(filename))
          if os.path.isabs(filename):
              return filename
          path_to_try = os.path.abspath(filename)
          if os.path.isfile(path_to_try):
              return path_to_try
          if sys.platform != 'win32':
              paths = [
                  os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')),
              ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
              for path in paths:
                  path_to_try = os.path.join(path, 'meson', 'cross', filename)
                  if os.path.isfile(path_to_try):
                      return path_to_try
              raise MesonException('Cannot find specified cross file: ' + filename)
  
          raise MesonException('Cannot find specified cross file: ' + filename)
  
      def sanitize_prefix(self, prefix):
          if not os.path.isabs(prefix):
              raise MesonException('prefix value {!r} must be an absolute path'
                                   ''.format(prefix))
          if prefix.endswith('/') or prefix.endswith('\\'):
              # On Windows we need to preserve the trailing slash if the
              # string is of type 'C:\' because 'C:' is not an absolute path.
              if len(prefix) == 3 and prefix[1] == ':':
                  pass
              # If prefix is a single character, preserve it since it is
              # the root directory.
              elif len(prefix) == 1:
                  pass
              else:
                  prefix = prefix[:-1]
          return prefix
  
      def sanitize_dir_option_value(self, prefix, option, value):
          '''
          If the option is an installation directory option and the value is an
          absolute path, check that it resides within prefix and return the value
          as a path relative to the prefix.
  
          This way everyone can do f.ex, get_option('libdir') and be sure to get
          the library directory relative to prefix.
          '''
          if option.endswith('dir') and os.path.isabs(value) and \
             option not in builtin_dir_noprefix_options:
              # Value must be a subdir of the prefix
              # commonpath will always return a path in the native format, so we
              # must use pathlib.PurePath to do the same conversion before
              # comparing.
              if os.path.commonpath([value, prefix]) != str(PurePath(prefix)):
                  m = 'The value of the {!r} option is {!r} which must be a ' \
                      'subdir of the prefix {!r}.\nNote that if you pass a ' \
                      'relative path, it is assumed to be a subdir of prefix.'
                  raise MesonException(m.format(option, value, prefix))
              # Convert path to be relative to prefix
              skip = len(prefix) + 1
              value = value[skip:]
          return value
  
      def init_builtins(self):
          # Create builtin options with default values
          self.builtins = {}
          prefix = get_builtin_option_default('prefix')
          for key in get_builtin_options():
              value = get_builtin_option_default(key, prefix)
              args = [key] + builtin_options[key][1:-1] + [value]
              self.builtins[key] = builtin_options[key][0](*args)
  
      def init_backend_options(self, backend_name):
          if backend_name == 'ninja':
              self.backend_options['backend_max_links'] = \
                  UserIntegerOption(
                      'backend_max_links',
                      'Maximum number of linker processes to run or 0 for no '
                      'limit',
                      0, None, 0)
          elif backend_name.startswith('vs'):
              self.backend_options['backend_startup_project'] = \
                  UserStringOption(
                      'backend_startup_project',
                      'Default project to execute in Visual Studio',
                      '')
  
      def get_builtin_option(self, optname):
          if optname in self.builtins:
              return self.builtins[optname].value
          raise RuntimeError('Tried to get unknown builtin option %s.' % optname)
  
      def set_builtin_option(self, optname, value):
          if optname == 'prefix':
              value = self.sanitize_prefix(value)
          elif optname in self.builtins:
              prefix = self.builtins['prefix'].value
              value = self.sanitize_dir_option_value(prefix, optname, value)
          else:
              raise RuntimeError('Tried to set unknown builtin option %s.' % optname)
          self.builtins[optname].set_value(value)
  
      def validate_option_value(self, option_name, override_value):
          for opts in (self.builtins, self.base_options, self.compiler_options, self.user_options):
              if option_name in opts:
                  opt = opts[option_name]
                  return opt.validate_value(override_value)
          raise MesonException('Tried to validate unknown option %s.' % option_name)
  
      def get_external_args(self, lang):
          return self.compiler_options[lang + '_args'].value
  
      def get_external_link_args(self, lang):
          return self.compiler_options[lang + '_link_args'].value
  
      def get_external_preprocess_args(self, lang):
          return self.external_preprocess_args[lang]
  
      def merge_user_options(self, options):
          for (name, value) in options.items():
              if name not in self.user_options:
                  self.user_options[name] = value
              else:
                  oldval = self.user_options[name]
                  if type(oldval) != type(value):
                      self.user_options[name] = value
  
      def set_options(self, options, subproject=''):
          # Set prefix first because it's needed to sanitize other options
          prefix = self.builtins['prefix'].value
          if 'prefix' in options:
              prefix = self.sanitize_prefix(options['prefix'])
              self.builtins['prefix'].set_value(prefix)
              for key in builtin_dir_noprefix_options:
                  if key not in options:
                      self.builtins[key].set_value(get_builtin_option_default(key, prefix))
  
          unknown_options = []
          for k, v in options.items():
              if k == 'prefix':
                  pass
              elif k in self.builtins:
                  tgt = self.builtins[k]
                  tgt.set_value(self.sanitize_dir_option_value(prefix, k, v))
              elif k in self.backend_options:
                  tgt = self.backend_options[k]
                  tgt.set_value(v)
              elif k in self.user_options:
                  tgt = self.user_options[k]
                  tgt.set_value(v)
              elif k in self.compiler_options:
                  tgt = self.compiler_options[k]
                  tgt.set_value(v)
              elif k in self.base_options:
                  tgt = self.base_options[k]
                  tgt.set_value(v)
              else:
                  unknown_options.append(k)
  
          if unknown_options:
              unknown_options = ', '.join(sorted(unknown_options))
              sub = 'In subproject {}: '.format(subproject) if subproject else ''
              mlog.warning('{}Unknown options: "{}"'.format(sub, unknown_options))
  
  def load(build_dir):
      filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
      load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename)
      try:
          with open(filename, 'rb') as f:
              obj = pickle.load(f)
      except pickle.UnpicklingError:
          raise MesonException(load_fail_msg)
      if not isinstance(obj, CoreData):
          raise MesonException(load_fail_msg)
      if obj.version != version:
          raise MesonException('Build directory has been generated with Meson version %s, which is incompatible with current version %s.\nPlease delete this build directory AND create a new one.' %
                               (obj.version, version))
      return obj
  
  def save(obj, build_dir):
      filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
      prev_filename = filename + '.prev'
      tempfilename = filename + '~'
      if obj.version != version:
          raise MesonException('Fatal version mismatch corruption.')
      if os.path.exists(filename):
          import shutil
          shutil.copyfile(filename, prev_filename)
      with open(tempfilename, 'wb') as f:
          pickle.dump(obj, f)
          f.flush()
          os.fsync(f.fileno())
      os.replace(tempfilename, filename)
      return filename
  
  def get_builtin_options():
      return list(builtin_options.keys())
  
  def is_builtin_option(optname):
      return optname in get_builtin_options()
  
  def get_builtin_option_choices(optname):
      if is_builtin_option(optname):
          if builtin_options[optname][0] == UserComboOption:
              return builtin_options[optname][2]
          elif builtin_options[optname][0] == UserBooleanOption:
              return [True, False]
          elif builtin_options[optname][0] == UserFeatureOption:
              return UserFeatureOption.static_choices
          else:
              return None
      else:
          raise RuntimeError('Tried to get the supported values for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_description(optname):
      if is_builtin_option(optname):
          return builtin_options[optname][1]
      else:
          raise RuntimeError('Tried to get the description for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_action(optname):
      default = builtin_options[optname][2]
      if default is True:
          return 'store_false'
      elif default is False:
          return 'store_true'
      return None
  
  def get_builtin_option_default(optname, prefix=''):
      if is_builtin_option(optname):
          o = builtin_options[optname]
          if o[0] == UserComboOption:
              return o[3]
          if o[0] == UserIntegerOption:
              return o[4]
          try:
              return builtin_dir_noprefix_options[optname][prefix]
          except KeyError:
              pass
          return o[2]
      else:
          raise RuntimeError('Tried to get the default value for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_cmdline_name(name):
      if name == 'warning_level':
          return '--warnlevel'
      else:
          return '--' + name.replace('_', '-')
  
  def add_builtin_argument(p, name):
      kwargs = {}
      c = get_builtin_option_choices(name)
      b = get_builtin_option_action(name)
      h = get_builtin_option_description(name)
      if not b:
          h = h.rstrip('.') + ' (default: %s).' % get_builtin_option_default(name)
      else:
          kwargs['action'] = b
      if c and not b:
          kwargs['choices'] = c
      kwargs['default'] = argparse.SUPPRESS
      kwargs['dest'] = name
  
      cmdline_name = get_builtin_option_cmdline_name(name)
      p.add_argument(cmdline_name, help=h, **kwargs)
  
  def register_builtin_arguments(parser):
      for n in builtin_options:
          add_builtin_argument(parser, n)
      parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option",
                          help='Set the value of an option, can be used several times to set multiple options.')
  
  def create_options_dict(options):
      result = {}
      for o in options:
          try:
              (key, value) = o.split('=', 1)
          except ValueError:
              raise MesonException('Option {!r} must have a value separated by equals sign.'.format(o))
          result[key] = value
      return result
  
  def parse_cmd_line_options(args):
      args.cmd_line_options = create_options_dict(args.projectoptions)
  
      # Merge builtin options set with --option into the dict.
      for name in builtin_options:
          value = getattr(args, name, None)
          if value is not None:
              if name in args.cmd_line_options:
                  cmdline_name = get_builtin_option_cmdline_name(name)
                  raise MesonException(
                      'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name))
              args.cmd_line_options[name] = value
              delattr(args, name)
  
  builtin_options = {
      'buildtype':  [UserComboOption, 'Build type to use.', ['plain', 'debug', 'debugoptimized', 'release', 'minsize'], 'debug'],
      'strip':      [UserBooleanOption, 'Strip targets on install.', False],
      'unity':      [UserComboOption, 'Unity build.', ['on', 'off', 'subprojects'], 'off'],
      'prefix':     [UserStringOption, 'Install
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/coredata.py
files.old_contents: # Copyright 2012-2018 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mlog
  import pickle, os, uuid, shlex
  import sys
  from pathlib import PurePath
  from collections import OrderedDict
  from .mesonlib import MesonException
  from .mesonlib import default_libdir, default_libexecdir, default_prefix
  import ast
  import argparse
  
  version = '0.48.0.dev1'
  backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode']
  
  default_yielding = False
  
  class UserOption:
      def __init__(self, name, description, choices, yielding):
          super().__init__()
          self.name = name
          self.choices = choices
          self.description = description
          if yielding is None:
              yielding = default_yielding
          if not isinstance(yielding, bool):
              raise MesonException('Value of "yielding" must be a boolean.')
          self.yielding = yielding
  
      # Check that the input is a valid value and return the
      # "cleaned" or "native" version. For example the Boolean
      # option could take the string "true" and return True.
      def validate_value(self, value):
          raise RuntimeError('Derived option class did not override validate_value.')
  
      def set_value(self, newvalue):
          self.value = self.validate_value(newvalue)
  
  class UserStringOption(UserOption):
      def __init__(self, name, description, value, choices=None, yielding=None):
          super().__init__(name, description, choices, yielding)
          self.set_value(value)
  
      def validate_value(self, value):
          if not isinstance(value, str):
              raise MesonException('Value "%s" for string option "%s" is not a string.' % (str(value), self.name))
          return value
  
  class UserBooleanOption(UserOption):
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, [True, False], yielding)
          self.set_value(value)
  
      def __bool__(self):
          return self.value
  
      def validate_value(self, value):
          if isinstance(value, bool):
              return value
          if value.lower() == 'true':
              return True
          if value.lower() == 'false':
              return False
          raise MesonException('Value %s is not boolean (true or false).' % value)
  
  class UserIntegerOption(UserOption):
      def __init__(self, name, description, min_value, max_value, value, yielding=None):
          super().__init__(name, description, [True, False], yielding)
          self.min_value = min_value
          self.max_value = max_value
          self.set_value(value)
          c = []
          if min_value is not None:
              c.append('>=' + str(min_value))
          if max_value is not None:
              c.append('<=' + str(max_value))
          self.choices = ', '.join(c)
  
      def validate_value(self, value):
          if isinstance(value, str):
              value = self.toint(value)
          if not isinstance(value, int):
              raise MesonException('New value for integer option is not an integer.')
          if self.min_value is not None and value < self.min_value:
              raise MesonException('New value %d is less than minimum value %d.' % (value, self.min_value))
          if self.max_value is not None and value > self.max_value:
              raise MesonException('New value %d is more than maximum value %d.' % (value, self.max_value))
          return value
  
      def toint(self, valuestring):
          try:
              return int(valuestring)
          except ValueError:
              raise MesonException('Value string "%s" is not convertable to an integer.' % valuestring)
  
  class UserUmaskOption(UserIntegerOption):
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, 0, 0o777, value, yielding)
          self.choices = ['preserve', '0000-0777']
  
      def validate_value(self, value):
          if value is None or value == 'preserve':
              return None
          return super().validate_value(value)
  
      def toint(self, valuestring):
          try:
              return int(valuestring, 8)
          except ValueError as e:
              raise MesonException('Invalid mode: {}'.format(e))
  
  class UserComboOption(UserOption):
      def __init__(self, name, description, choices, value, yielding=None):
          super().__init__(name, description, choices, yielding)
          if not isinstance(self.choices, list):
              raise MesonException('Combo choices must be an array.')
          for i in self.choices:
              if not isinstance(i, str):
                  raise MesonException('Combo choice elements must be strings.')
          self.set_value(value)
  
      def validate_value(self, value):
          if value not in self.choices:
              optionsstring = ', '.join(['"%s"' % (item,) for item in self.choices])
              raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (value, self.name, optionsstring))
          return value
  
  class UserArrayOption(UserOption):
      def __init__(self, name, description, value, shlex_split=False, user_input=False, **kwargs):
          super().__init__(name, description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None))
          self.shlex_split = shlex_split
          self.value = self.validate_value(value, user_input=user_input)
  
      def validate_value(self, value, user_input=True):
          # User input is for options defined on the command line (via -D
          # options). Users can put their input in as a comma separated
          # string, but for defining options in meson_options.txt the format
          # should match that of a combo
          if not user_input and isinstance(value, str) and not value.startswith('['):
              raise MesonException('Value does not define an array: ' + value)
  
          if isinstance(value, str):
              if value.startswith('['):
                  newvalue = ast.literal_eval(value)
              elif value == '':
                  newvalue = []
              else:
                  if self.shlex_split:
                      newvalue = shlex.split(value)
                  else:
                      newvalue = [v.strip() for v in value.split(',')]
          elif isinstance(value, list):
              newvalue = value
          else:
              raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue)))
  
          if len(set(newvalue)) != len(newvalue):
              msg = 'Duplicated values in array option "%s" is deprecated. ' \
                    'This will become a hard error in the future.' % (self.name)
              mlog.log(mlog.red('DEPRECATION:'), msg)
          for i in newvalue:
              if not isinstance(i, str):
                  raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue)))
          if self.choices:
              bad = [x for x in newvalue if x not in self.choices]
              if bad:
                  raise MesonException('Options "{}" are not in allowed choices: "{}"'.format(
                      ', '.join(bad), ', '.join(self.choices)))
          return newvalue
  
  class UserFeatureOption(UserComboOption):
      static_choices = ['enabled', 'disabled', 'auto']
  
      def __init__(self, name, description, value, yielding=None):
          super().__init__(name, description, self.static_choices, value, yielding)
  
      def is_enabled(self):
          return self.value == 'enabled'
  
      def is_disabled(self):
          return self.value == 'disabled'
  
      def is_auto(self):
          return self.value == 'auto'
  
  # This class contains all data that must persist over multiple
  # invocations of Meson. It is roughly the same thing as
  # cmakecache.
  
  class CoreData:
  
      def __init__(self, options):
          self.lang_guids = {
              'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'cpp': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942',
              'test': '3AC096D0-A1C2-E12C-1390-A8335801FDAB',
              'directory': '2150E333-8FDC-42A3-9474-1A3956D46DE8',
          }
          self.test_guid = str(uuid.uuid4()).upper()
          self.regen_guid = str(uuid.uuid4()).upper()
          self.target_guids = {}
          self.version = version
          self.init_builtins()
          self.backend_options = {}
          self.user_options = {}
          self.compiler_options = {}
          self.base_options = {}
          self.external_preprocess_args = {} # CPPFLAGS only
          self.cross_file = self.__load_cross_file(options.cross_file)
          self.wrap_mode = options.wrap_mode
          self.compilers = OrderedDict()
          self.cross_compilers = OrderedDict()
          self.deps = OrderedDict()
          # Only to print a warning if it changes between Meson invocations.
          self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '')
  
      @staticmethod
      def __load_cross_file(filename):
          """Try to load the cross file.
  
          If the filename is None return None. If the filename is an absolute
          (after resolving variables and ~), return that absolute path. Next,
          check if the file is relative to the current source dir. If the path
          still isn't resolved do the following:
              Windows:
                  - Error
              *:
                  - $XDG_DATA_HOME/meson/cross (or ~/.local/share/meson/cross if
                    undefined)
                  - $XDG_DATA_DIRS/meson/cross (or
                    /usr/local/share/meson/cross:/usr/share/meson/cross if undefined)
                  - Error
  
          Non-Windows follows the Linux path and will honor XDG_* if set. This
          simplifies the implementation somewhat.
          """
          if filename is None:
              return None
          filename = os.path.expanduser(os.path.expandvars(filename))
          if os.path.isabs(filename):
              return filename
          path_to_try = os.path.abspath(filename)
          if os.path.isfile(path_to_try):
              return path_to_try
          if sys.platform != 'win32':
              paths = [
                  os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')),
              ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
              for path in paths:
                  path_to_try = os.path.join(path, 'meson', 'cross', filename)
                  if os.path.isfile(path_to_try):
                      return path_to_try
              raise MesonException('Cannot find specified cross file: ' + filename)
  
          raise MesonException('Cannot find specified cross file: ' + filename)
  
      def sanitize_prefix(self, prefix):
          if not os.path.isabs(prefix):
              raise MesonException('prefix value {!r} must be an absolute path'
                                   ''.format(prefix))
          if prefix.endswith('/') or prefix.endswith('\\'):
              # On Windows we need to preserve the trailing slash if the
              # string is of type 'C:\' because 'C:' is not an absolute path.
              if len(prefix) == 3 and prefix[1] == ':':
                  pass
              # If prefix is a single character, preserve it since it is
              # the root directory.
              elif len(prefix) == 1:
                  pass
              else:
                  prefix = prefix[:-1]
          return prefix
  
      def sanitize_dir_option_value(self, prefix, option, value):
          '''
          If the option is an installation directory option and the value is an
          absolute path, check that it resides within prefix and return the value
          as a path relative to the prefix.
  
          This way everyone can do f.ex, get_option('libdir') and be sure to get
          the library directory relative to prefix.
          '''
          if option.endswith('dir') and os.path.isabs(value) and \
             option not in builtin_dir_noprefix_options:
              # Value must be a subdir of the prefix
              # commonpath will always return a path in the native format, so we
              # must use pathlib.PurePath to do the same conversion before
              # comparing.
              if os.path.commonpath([value, prefix]) != str(PurePath(prefix)):
                  m = 'The value of the {!r} option is {!r} which must be a ' \
                      'subdir of the prefix {!r}.\nNote that if you pass a ' \
                      'relative path, it is assumed to be a subdir of prefix.'
                  raise MesonException(m.format(option, value, prefix))
              # Convert path to be relative to prefix
              skip = len(prefix) + 1
              value = value[skip:]
          return value
  
      def init_builtins(self):
          # Create builtin options with default values
          self.builtins = {}
          prefix = get_builtin_option_default('prefix')
          for key in get_builtin_options():
              value = get_builtin_option_default(key, prefix)
              args = [key] + builtin_options[key][1:-1] + [value]
              self.builtins[key] = builtin_options[key][0](*args)
  
      def init_backend_options(self, backend_name):
          if backend_name == 'ninja':
              self.backend_options['backend_max_links'] = \
                  UserIntegerOption(
                      'backend_max_links',
                      'Maximum number of linker processes to run or 0 for no '
                      'limit',
                      0, None, 0)
          elif backend_name.startswith('vs'):
              self.backend_options['backend_startup_project'] = \
                  UserStringOption(
                      'backend_startup_project',
                      'Default project to execute in Visual Studio',
                      '')
  
      def get_builtin_option(self, optname):
          if optname in self.builtins:
              return self.builtins[optname].value
          raise RuntimeError('Tried to get unknown builtin option %s.' % optname)
  
      def set_builtin_option(self, optname, value):
          if optname == 'prefix':
              value = self.sanitize_prefix(value)
          elif optname in self.builtins:
              prefix = self.builtins['prefix'].value
              value = self.sanitize_dir_option_value(prefix, optname, value)
          else:
              raise RuntimeError('Tried to set unknown builtin option %s.' % optname)
          self.builtins[optname].set_value(value)
  
      def validate_option_value(self, option_name, override_value):
          for opts in (self.builtins, self.base_options, self.compiler_options, self.user_options):
              if option_name in opts:
                  opt = opts[option_name]
                  return opt.validate_value(override_value)
          raise MesonException('Tried to validate unknown option %s.' % option_name)
  
      def get_external_args(self, lang):
          return self.compiler_options[lang + '_args'].value
  
      def get_external_link_args(self, lang):
          return self.compiler_options[lang + '_link_args'].value
  
      def get_external_preprocess_args(self, lang):
          return self.external_preprocess_args[lang]
  
      def merge_user_options(self, options):
          for (name, value) in options.items():
              if name not in self.user_options:
                  self.user_options[name] = value
              else:
                  oldval = self.user_options[name]
                  if type(oldval) != type(value):
                      self.user_options[name] = value
  
      def set_options(self, options, subproject=''):
          # Set prefix first because it's needed to sanitize other options
          prefix = self.builtins['prefix'].value
          if 'prefix' in options:
              prefix = self.sanitize_prefix(options['prefix'])
              self.builtins['prefix'].set_value(prefix)
              for key in builtin_dir_noprefix_options:
                  if key not in options:
                      self.builtins[key].set_value(get_builtin_option_default(key, prefix))
  
          unknown_options = []
          for k, v in options.items():
              if k == 'prefix':
                  pass
              elif k in self.builtins:
                  tgt = self.builtins[k]
                  tgt.set_value(self.sanitize_dir_option_value(prefix, k, v))
              elif k in self.backend_options:
                  tgt = self.backend_options[k]
                  tgt.set_value(v)
              elif k in self.user_options:
                  tgt = self.user_options[k]
                  tgt.set_value(v)
              elif k in self.compiler_options:
                  tgt = self.compiler_options[k]
                  tgt.set_value(v)
              elif k in self.base_options:
                  tgt = self.base_options[k]
                  tgt.set_value(v)
              else:
                  unknown_options.append(k)
  
          if unknown_options:
              unknown_options = ', '.join(sorted(unknown_options))
              sub = 'In subproject {}: '.format(subproject) if subproject else ''
              mlog.warning('{}Unknown options: "{}"'.format(sub, unknown_options))
  
  def load(build_dir):
      filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
      load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename)
      try:
          with open(filename, 'rb') as f:
              obj = pickle.load(f)
      except pickle.UnpicklingError:
          raise MesonException(load_fail_msg)
      if not isinstance(obj, CoreData):
          raise MesonException(load_fail_msg)
      if obj.version != version:
          raise MesonException('Build directory has been generated with Meson version %s, which is incompatible with current version %s.\nPlease delete this build directory AND create a new one.' %
                               (obj.version, version))
      return obj
  
  def save(obj, build_dir):
      filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
      prev_filename = filename + '.prev'
      tempfilename = filename + '~'
      if obj.version != version:
          raise MesonException('Fatal version mismatch corruption.')
      if os.path.exists(filename):
          import shutil
          shutil.copyfile(filename, prev_filename)
      with open(tempfilename, 'wb') as f:
          pickle.dump(obj, f)
          f.flush()
          os.fsync(f.fileno())
      os.replace(tempfilename, filename)
      return filename
  
  def get_builtin_options():
      return list(builtin_options.keys())
  
  def is_builtin_option(optname):
      return optname in get_builtin_options()
  
  def get_builtin_option_choices(optname):
      if is_builtin_option(optname):
          if builtin_options[optname][0] == UserComboOption:
              return builtin_options[optname][2]
          elif builtin_options[optname][0] == UserBooleanOption:
              return [True, False]
          elif builtin_options[optname][0] == UserFeatureOption:
              return UserFeatureOption.static_choices
          else:
              return None
      else:
          raise RuntimeError('Tried to get the supported values for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_description(optname):
      if is_builtin_option(optname):
          return builtin_options[optname][1]
      else:
          raise RuntimeError('Tried to get the description for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_action(optname):
      default = builtin_options[optname][2]
      if default is True:
          return 'store_false'
      elif default is False:
          return 'store_true'
      return None
  
  def get_builtin_option_default(optname, prefix=''):
      if is_builtin_option(optname):
          o = builtin_options[optname]
          if o[0] == UserComboOption:
              return o[3]
          if o[0] == UserIntegerOption:
              return o[4]
          try:
              return builtin_dir_noprefix_options[optname][prefix]
          except KeyError:
              pass
          return o[2]
      else:
          raise RuntimeError('Tried to get the default value for an unknown builtin option \'%s\'.' % optname)
  
  def get_builtin_option_cmdline_name(name):
      if name == 'warning_level':
          return '--warnlevel'
      else:
          return '--' + name.replace('_', '-')
  
  def add_builtin_argument(p, name):
      kwargs = {}
      c = get_builtin_option_choices(name)
      b = get_builtin_option_action(name)
      h = get_builtin_option_description(name)
      if not b:
          h = h.rstrip('.') + ' (default: %s).' % get_builtin_option_default(name)
      else:
          kwargs['action'] = b
      if c and not b:
          kwargs['choices'] = c
      kwargs['default'] = argparse.SUPPRESS
      kwargs['dest'] = name
  
      cmdline_name = get_builtin_option_cmdline_name(name)
      p.add_argument(cmdline_name, help=h, **kwargs)
  
  def register_builtin_arguments(parser):
      for n in builtin_options:
          add_builtin_argument(parser, n)
      parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option",
                          help='Set the value of an option, can be used several times to set multiple options.')
  
  def create_options_dict(options):
      result = {}
      for o in options:
          try:
              (key, value) = o.split('=', 1)
          except ValueError:
              raise MesonException('Option {!r} must have a value separated by equals sign.'.format(o))
          result[key] = value
      return result
  
  def parse_cmd_line_options(args):
      args.cmd_line_options = create_options_dict(args.projectoptions)
  
      # Merge builtin options set with --option into the dict.
      for name in builtin_options:
          value = getattr(args, name, None)
          if value is not None:
              if name in args.cmd_line_options:
                  cmdline_name = get_builtin_option_cmdline_name(name)
                  raise MesonException(
                      'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name))
              args.cmd_line_options[name] = value
              delattr(args, name)
  
  builtin_options = {
      'buildtype':  [UserComboOption, 'Build type to use.', ['plain', 'debug', 'debugoptimized', 'release', 'minsize'], 'debug'],
      'strip':      [UserBooleanOption, 'Strip targets on install.', False],
      'unity':      [UserComboOption, 'Unity build.', ['on', 'off', 'subprojects'], 'off'],
      'prefix':    
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/coredata.py
files.new_contents: # Copyright 2012-2018 The Meson development team
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mparser
  from . import environment
  from . import coredata
  from . import dependencies
  from . import mlog
  from . import build
  from . import optinterpreter
  from . import compilers
  from .wrap import wrap, WrapMode
  from . import mesonlib
  from .mesonlib import FileMode, Popen_safe, listify, extract_as_list, has_path_sep
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  from .interpreterbase import InterpreterBase
  from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
  from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
  from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .modules import ModuleReturnValue
  
  import os, sys, shutil, uuid
  import re, shlex
  import subprocess
  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, subproject=None):
          self.held_object = obj
          self.subproject = subproject
  
      def __repr__(self):
          return '<Holder: {!r}>'.format(self.held_object)
  
  class FeatureOptionHolder(InterpreterObject, ObjectHolder):
      def __init__(self, env, option):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, option)
          if option.is_auto():
              self.held_object = env.coredata.builtins['auto_features']
          self.name = option.name
          self.methods.update({'enabled': self.enabled_method,
                               'disabled': self.disabled_method,
                               'auto': self.auto_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def enabled_method(self, args, kwargs):
          return self.held_object.is_enabled()
  
      @noPosargs
      @permittedKwargs({})
      def disabled_method(self, args, kwargs):
          return self.held_object.is_disabled()
  
      @noPosargs
      @permittedKwargs({})
      def auto_method(self, args, kwargs):
          return self.held_object.is_auto()
  
  def extract_required_kwarg(kwargs):
      val = kwargs.get('required', True)
      disabled = False
      required = False
      feature = None
      if isinstance(val, FeatureOptionHolder):
          option = val.held_object
          feature = val.name
          if option.is_disabled():
              disabled = True
          elif option.is_enabled():
              required = True
      elif isinstance(required, bool):
          required = val
      else:
          raise InterpreterException('required keyword argument must be boolean or a feature option')
  
      # Keep boolean value in kwargs to simplify other places where this kwarg is
      # checked.
      kwargs['required'] = required
  
      return disabled, required, feature
  
  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, capture=True):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          self.capture = capture
          pc, self.stdout, self.stderr = self.run_command(cmd, args, 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)
          stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
              if self.capture:
                  mlog.debug('--- stdout ---')
                  mlog.debug(o)
              else:
                  o = ''
                  mlog.debug('--- stdout disabled ---')
              mlog.debug('--- stderr ---')
              mlog.debug(e)
              mlog.debug('')
  
              if check and p.returncode != 0:
                  raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
  
              return p, o, e
          except FileNotFoundError:
              raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.returncode
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.stderr
  
  class ConfigureFileHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          InterpreterObject.__init__(self)
          obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data)
          ObjectHolder.__init__(self, obj)
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self):
          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, pv):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData(), pv)
          self.methods.update({'set': self.set_method,
                               'set10': self.set10_method,
                               'set_quoted': self.set_quoted_method,
                               'has': self.has_method,
                               'get': self.get_method,
                               'get_unquoted': self.get_unquoted_method,
                               'merge_from': self.merge_from_method,
                               })
  
      def is_used(self):
          return self.used
  
      def mark_used(self):
          self.used = True
  
      def validate_args(self, args, kwargs):
          if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2:
              mlog.deprecation('''Passing a list as the single argument to configuration_data.set is deprecated.
  This will become a hard error in the future''')
              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('configuration_data.get_unquoted()', '0.44.0')
      def get_unquoted_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              val = self.held_object.get(name)[0]
          elif len(args) > 1:
              val = args[1]
          else:
              raise InterpreterException('Entry %s not in configuration data.' % name)
          if val[0] == '"' and val[-1] == '"':
              return val[1:-1]
          return val
  
      def get(self, name):
          return self.held_object.values[name] # (val, desc)
  
      def keys(self):
          return self.held_object.values.keys()
  
      def merge_from_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Merge_from takes one positional argument.')
          from_object = args[0]
          if not isinstance(from_object, ConfigurationDataHolder):
              raise InterpreterException('Merge_from argument must be a configuration data object.')
          from_object = from_object.held_object
          for k, v in from_object.values.items():
              self.held_object.values[k] = v
  
  # Interpreter objects can not be pickled so we must have
  # these wrappers.
  
  class DependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               '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('dep.get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class InternalDependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          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):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  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, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el, pv)
          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):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      @FeatureNewKwargs('generator', '0.43.0', ['capture'])
      def __init__(self, interp, args, kwargs):
          self.interpreter = interp
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject)
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=[]):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  class BuildMachine(InterpreterObject, ObjectHolder):
      def __init__(self, compilers):
          self.compilers = compilers
          InterpreterObject.__init__(self)
          held_object = environment.MachineInfo(environment.detect_system(),
                                                environment.detect_cpu_family(self.compilers),
                                                environment.detect_cpu(self.compilers),
                                                sys.byteorder)
          ObjectHolder.__init__(self, held_object)
          self.methods.update({'system': self.system_method,
                               'cpu_family': self.cpu_family_method,
                               'cpu': self.cpu_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  # This class will provide both host_machine and
  # target_machine
  class CrossMachineInfo(InterpreterObject, ObjectHolder):
      def __init__(self, cross_info):
          InterpreterObject.__init__(self)
          minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'}
          if set(cross_info) < minimum_cross_info:
              raise InterpreterException(
                  'Machine info is currently {}\n'.format(cross_info) +
                  'but is missing {}.'.format(minimum_cross_info - set(cross_info)))
          self.info = cross_info
  
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/interpreter.py
files.old_contents: # Copyright 2012-2018 The Meson development team
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import mparser
  from . import environment
  from . import coredata
  from . import dependencies
  from . import mlog
  from . import build
  from . import optinterpreter
  from . import compilers
  from .wrap import wrap, WrapMode
  from . import mesonlib
  from .mesonlib import FileMode, Popen_safe, listify, extract_as_list, has_path_sep
  from .dependencies import ExternalProgram
  from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
  from .interpreterbase import InterpreterBase
  from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
  from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
  from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler
  from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
  from .modules import ModuleReturnValue
  
  import os, sys, shutil, uuid
  import re, shlex
  import subprocess
  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, subproject=None):
          self.held_object = obj
          self.subproject = subproject
  
      def __repr__(self):
          return '<Holder: {!r}>'.format(self.held_object)
  
  class FeatureOptionHolder(InterpreterObject, ObjectHolder):
      def __init__(self, env, option):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, option)
          if option.is_auto():
              self.held_object = env.coredata.builtins['auto_features']
          self.name = option.name
          self.methods.update({'enabled': self.enabled_method,
                               'disabled': self.disabled_method,
                               'auto': self.auto_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def enabled_method(self, args, kwargs):
          return self.held_object.is_enabled()
  
      @noPosargs
      @permittedKwargs({})
      def disabled_method(self, args, kwargs):
          return self.held_object.is_disabled()
  
      @noPosargs
      @permittedKwargs({})
      def auto_method(self, args, kwargs):
          return self.held_object.is_auto()
  
  def extract_required_kwarg(kwargs):
      val = kwargs.get('required', True)
      disabled = False
      required = False
      feature = None
      if isinstance(val, FeatureOptionHolder):
          option = val.held_object
          feature = val.name
          if option.is_disabled():
              disabled = True
          elif option.is_enabled():
              required = True
      elif isinstance(required, bool):
          required = val
      else:
          raise InterpreterException('required keyword argument must be boolean or a feature option')
  
      # Keep boolean value in kwargs to simplify other places where this kwarg is
      # checked.
      kwargs['required'] = required
  
      return disabled, required, feature
  
  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, capture=True):
          super().__init__()
          if not isinstance(cmd, ExternalProgram):
              raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
          self.capture = capture
          pc, self.stdout, self.stderr = self.run_command(cmd, args, 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)
          stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
          mlog.debug('Running command:', ' '.join(command_array))
          try:
              p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
              if self.capture:
                  mlog.debug('--- stdout ---')
                  mlog.debug(o)
              else:
                  o = ''
                  mlog.debug('--- stdout disabled ---')
              mlog.debug('--- stderr ---')
              mlog.debug(e)
              mlog.debug('')
  
              if check and p.returncode != 0:
                  raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
  
              return p, o, e
          except FileNotFoundError:
              raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
  
      @noPosargs
      @permittedKwargs({})
      def returncode_method(self, args, kwargs):
          return self.returncode
  
      @noPosargs
      @permittedKwargs({})
      def stdout_method(self, args, kwargs):
          return self.stdout
  
      @noPosargs
      @permittedKwargs({})
      def stderr_method(self, args, kwargs):
          return self.stderr
  
  class ConfigureFileHolder(InterpreterObject, ObjectHolder):
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          InterpreterObject.__init__(self)
          obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data)
          ObjectHolder.__init__(self, obj)
  
  
  class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder):
      def __init__(self):
          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, pv):
          MutableInterpreterObject.__init__(self)
          self.used = False # These objects become immutable after use in configure_file.
          ObjectHolder.__init__(self, build.ConfigurationData(), pv)
          self.methods.update({'set': self.set_method,
                               'set10': self.set10_method,
                               'set_quoted': self.set_quoted_method,
                               'has': self.has_method,
                               'get': self.get_method,
                               'get_unquoted': self.get_unquoted_method,
                               'merge_from': self.merge_from_method,
                               })
  
      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('configuration_data.get_unquoted()', '0.44.0')
      def get_unquoted_method(self, args, kwargs):
          if len(args) < 1 or len(args) > 2:
              raise InterpreterException('Get method takes one or two arguments.')
          name = args[0]
          if name in self.held_object:
              val = self.held_object.get(name)[0]
          elif len(args) > 1:
              val = args[1]
          else:
              raise InterpreterException('Entry %s not in configuration data.' % name)
          if val[0] == '"' and val[-1] == '"':
              return val[1:-1]
          return val
  
      def get(self, name):
          return self.held_object.values[name] # (val, desc)
  
      def keys(self):
          return self.held_object.values.keys()
  
      def merge_from_method(self, args, kwargs):
          if len(args) != 1:
              raise InterpreterException('Merge_from takes one positional argument.')
          from_object = args[0]
          if not isinstance(from_object, ConfigurationDataHolder):
              raise InterpreterException('Merge_from argument must be a configuration data object.')
          from_object = from_object.held_object
          for k, v in from_object.values.items():
              self.held_object.values[k] = v
  
  # Interpreter objects can not be pickled so we must have
  # these wrappers.
  
  class DependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          self.methods.update({'found': self.found_method,
                               'type_name': self.type_name_method,
                               'version': self.version_method,
                               '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('dep.get_configtool_variable', '0.44.0')
      @permittedKwargs({})
      def configtool_method(self, args, kwargs):
          args = listify(args)
          if len(args) != 1:
              raise InterpreterException('get_configtool_variable takes exactly one argument.')
          varname = args[0]
          if not isinstance(varname, str):
              raise InterpreterException('Variable name must be a string.')
          return self.held_object.get_configtool_variable(varname)
  
      @FeatureNew('dep.partial_dependency', '0.46.0')
      @noPosargs
      @permittedKwargs(permitted_method_kwargs['partial_dependency'])
      def partial_dependency_method(self, args, kwargs):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class InternalDependencyHolder(InterpreterObject, ObjectHolder):
      def __init__(self, dep, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, dep, pv)
          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):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  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, pv):
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, el, pv)
          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):
          pdep = self.held_object.get_partial_dependency(**kwargs)
          return DependencyHolder(pdep, self.subproject)
  
  class GeneratorHolder(InterpreterObject, ObjectHolder):
      @FeatureNewKwargs('generator', '0.43.0', ['capture'])
      def __init__(self, interp, args, kwargs):
          self.interpreter = interp
          InterpreterObject.__init__(self)
          ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject)
          self.methods.update({'process': self.process_method})
  
      @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from'])
      @permittedKwargs({'extra_args', 'preserve_path_from'})
      def process_method(self, args, kwargs):
          extras = mesonlib.stringlistify(kwargs.get('extra_args', []))
          if 'preserve_path_from' in kwargs:
              preserve_path_from = kwargs['preserve_path_from']
              if not isinstance(preserve_path_from, str):
                  raise InvalidArguments('Preserve_path_from must be a string.')
              preserve_path_from = os.path.normpath(preserve_path_from)
              if not os.path.isabs(preserve_path_from):
                  # This is a bit of a hack. Fix properly before merging.
                  raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.')
          else:
              preserve_path_from = None
          gl = self.held_object.process_files('Generator', args, self.interpreter,
                                              preserve_path_from, extra_args=extras)
          return GeneratedListHolder(gl)
  
  
  class GeneratedListHolder(InterpreterObject, ObjectHolder):
      def __init__(self, arg1, extra_args=[]):
          InterpreterObject.__init__(self)
          if isinstance(arg1, GeneratorHolder):
              ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args))
          else:
              ObjectHolder.__init__(self, arg1)
  
      def __repr__(self):
          r = '<{}: {!r}>'
          return r.format(self.__class__.__name__, self.held_object.get_outputs())
  
      def add_file(self, a):
          self.held_object.add_file(a)
  
  class BuildMachine(InterpreterObject, ObjectHolder):
      def __init__(self, compilers):
          self.compilers = compilers
          InterpreterObject.__init__(self)
          held_object = environment.MachineInfo(environment.detect_system(),
                                                environment.detect_cpu_family(self.compilers),
                                                environment.detect_cpu(self.compilers),
                                                sys.byteorder)
          ObjectHolder.__init__(self, held_object)
          self.methods.update({'system': self.system_method,
                               'cpu_family': self.cpu_family_method,
                               'cpu': self.cpu_method,
                               'endian': self.endian_method,
                               })
  
      @noPosargs
      @permittedKwargs({})
      def cpu_family_method(self, args, kwargs):
          return self.held_object.cpu_family
  
      @noPosargs
      @permittedKwargs({})
      def cpu_method(self, args, kwargs):
          return self.held_object.cpu
  
      @noPosargs
      @permittedKwargs({})
      def system_method(self, args, kwargs):
          return self.held_object.system
  
      @noPosargs
      @permittedKwargs({})
      def endian_method(self, args, kwargs):
          return self.held_object.endian
  
  # This class will provide both host_machine and
  # target_machine
  class CrossMachineInfo(InterpreterObject, ObjectHolder):
      def __init__(self, cross_info):
          InterpreterObject.__init__(self)
          minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'}
          if set(cross_info) < minimum_cross_info:
              raise InterpreterException(
                  'Machine info is currently {}\n'.format(cross_info) +
                  'but is missing {}.'.format(minimum_cross_info - set(cross_info)))
  
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/interpreter.py
files.new_contents: # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # This class contains the basic functionality needed to run any interpreter
  # or an interpreter-based tool.
  
  from . import mparser, mesonlib, mlog
  from . import environment, dependencies
  
  import os, copy, re, types
  from functools import wraps
  
  # Decorators for method calls.
  
  def check_stringlist(a, msg='Arguments must be strings.'):
      if not isinstance(a, list):
          mlog.debug('Not a list:', str(a))
          raise InvalidArguments('Argument not a list.')
      if not all(isinstance(s, str) for s in a):
          mlog.debug('Element not a string:', str(a))
          raise InvalidArguments(msg)
  
  def _get_callee_args(wrapped_args, want_subproject=False):
      s = wrapped_args[0]
      n = len(wrapped_args)
      # Raise an error if the codepaths are not there
      subproject = None
      if want_subproject and n == 2:
          if hasattr(s, 'subproject'):
              # Interpreter base types have 2 args: self, node
              node_or_state = wrapped_args[1]
              # args and kwargs are inside the node
              args = None
              kwargs = None
              subproject = s.subproject
          elif hasattr(wrapped_args[1], 'subproject'):
              # Module objects have 2 args: self, interpreter
              node_or_state = wrapped_args[1]
              # args and kwargs are inside the node
              args = None
              kwargs = None
              subproject = wrapped_args[1].subproject
          else:
              raise AssertionError('Unknown args: {!r}'.format(wrapped_args))
      elif n == 3:
          # Methods on objects (*Holder, MesonMain, etc) have 3 args: self, args, kwargs
          node_or_state = None # FIXME
          args = wrapped_args[1]
          kwargs = wrapped_args[2]
          if want_subproject:
              if hasattr(s, 'subproject'):
                  subproject = s.subproject
              elif hasattr(s, 'interpreter'):
                  subproject = s.interpreter.subproject
      elif n == 4:
          # Meson functions have 4 args: self, node, args, kwargs
          # Module functions have 4 args: self, state, args, kwargs; except,
          # PythonInstallation methods have self, interpreter, args, kwargs
          node_or_state = wrapped_args[1]
          args = wrapped_args[2]
          kwargs = wrapped_args[3]
          if want_subproject:
              if isinstance(s, InterpreterBase):
                  subproject = s.subproject
              else:
                  subproject = node_or_state.subproject
      elif n == 5:
          # Module snippets have 5 args: self, interpreter, state, args, kwargs
          node_or_state = wrapped_args[2]
          args = wrapped_args[3]
          kwargs = wrapped_args[4]
          if want_subproject:
              subproject = node_or_state.subproject
      else:
          raise AssertionError('Unknown args: {!r}'.format(wrapped_args))
      # Sometimes interpreter methods are called internally with None instead of
      # empty list/dict
      args = args if args is not None else []
      kwargs = kwargs if kwargs is not None else {}
      return s, node_or_state, args, kwargs, subproject
  
  def flatten(args):
      if isinstance(args, mparser.StringNode):
          return args.value
      if isinstance(args, (int, str, mesonlib.File, InterpreterObject)):
          return args
      result = []
      for a in args:
          if isinstance(a, list):
              rest = flatten(a)
              result = result + rest
          elif isinstance(a, mparser.StringNode):
              result.append(a.value)
          else:
              result.append(a)
      return result
  
  def noPosargs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          args = _get_callee_args(wrapped_args)[2]
          if args:
              raise InvalidArguments('Function does not take positional arguments.')
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def noKwargs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          kwargs = _get_callee_args(wrapped_args)[3]
          if kwargs:
              raise InvalidArguments('Function does not take keyword arguments.')
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def stringArgs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          args = _get_callee_args(wrapped_args)[2]
          assert(isinstance(args, list))
          check_stringlist(args)
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def noArgsFlattening(f):
      setattr(f, 'no-args-flattening', True)
      return f
  
  class permittedKwargs:
  
      def __init__(self, permitted):
          self.permitted = permitted
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              s, node_or_state, args, kwargs, _ = _get_callee_args(wrapped_args)
              loc = types.SimpleNamespace()
              if hasattr(s, 'subdir'):
                  loc.subdir = s.subdir
                  loc.lineno = s.current_lineno
              elif node_or_state and hasattr(node_or_state, 'subdir'):
                  loc.subdir = node_or_state.subdir
                  loc.lineno = node_or_state.current_lineno
              else:
                  loc = None
              for k in kwargs:
                  if k not in self.permitted:
                      mlog.warning('''Passed invalid keyword argument "{}".'''.format(k), location=loc)
                      mlog.warning('This will become a hard error in the future.')
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  
  class FeatureCheckBase:
      "Base class for feature version checks"
  
      def __init__(self, feature_name, version):
          self.feature_name = feature_name
          self.feature_version = version
  
      @staticmethod
      def get_target_version(subproject):
          return mesonlib.project_meson_versions[subproject]
  
      def use(self, subproject):
          tv = self.get_target_version(subproject)
          # No target version
          if tv == '':
              return
          # Target version is new enough
          if mesonlib.version_compare_condition_with_min(tv, self.feature_version):
              return
          # Feature is too new for target version, register it
          if subproject not in self.feature_registry:
              self.feature_registry[subproject] = {self.feature_version: set()}
          register = self.feature_registry[subproject]
          if self.feature_version not in register:
              register[self.feature_version] = set()
          if self.feature_name in register[self.feature_version]:
              # Don't warn about the same feature multiple times
              # FIXME: This is needed to prevent duplicate warnings, but also
              # means we won't warn about a feature used in multiple places.
              return
          register[self.feature_version].add(self.feature_name)
          self.log_usage_warning(tv)
  
      @classmethod
      def report(cls, subproject):
          if subproject not in cls.feature_registry:
              return
          warning_str = cls.get_warning_str_prefix(cls.get_target_version(subproject))
          fv = cls.feature_registry[subproject]
          for version in sorted(fv.keys()):
              warning_str += '\n * {}: {}'.format(version, fv[version])
          mlog.warning(warning_str)
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              subproject = _get_callee_args(wrapped_args, want_subproject=True)[4]
              if subproject is None:
                  raise AssertionError('{!r}'.format(wrapped_args))
              self.use(subproject)
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  class FeatureNew(FeatureCheckBase):
      """Checks for new features"""
      # Class variable, shared across all instances
      #
      # Format: {subproject: {feature_version: set(feature_names)}}
      feature_registry = {}
  
      @staticmethod
      def get_warning_str_prefix(tv):
          return 'Project specifies a minimum meson_version \'{}\' which conflicts with:'.format(tv)
  
      def log_usage_warning(self, tv):
          mlog.warning('Project targetting \'{}\' but tried to use feature introduced '
                       'in \'{}\': {}'.format(tv, self.feature_version, self.feature_name))
  
  class FeatureDeprecated(FeatureCheckBase):
      """Checks for deprecated features"""
      # Class variable, shared across all instances
      #
      # Format: {subproject: {feature_version: set(feature_names)}}
      feature_registry = {}
  
      @staticmethod
      def get_warning_str_prefix(tv):
          return 'Deprecated features used:'
  
      def log_usage_warning(self, tv):
          mlog.deprecation('Project targetting \'{}\' but tried to use feature '
                           'deprecated since \'{}\': {}'
                           ''.format(tv, self.feature_version, self.feature_name))
  
  
  class FeatureCheckKwargsBase:
      def __init__(self, feature_name, feature_version, kwargs):
          self.feature_name = feature_name
          self.feature_version = feature_version
          self.kwargs = kwargs
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              # Which FeatureCheck class to invoke
              FeatureCheckClass = self.feature_check_class
              kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5]
              if subproject is None:
                  raise AssertionError('{!r}'.format(wrapped_args))
              for arg in self.kwargs:
                  if arg not in kwargs:
                      continue
                  name = arg + ' arg in ' + self.feature_name
                  FeatureCheckClass(name, self.feature_version).use(subproject)
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  class FeatureNewKwargs(FeatureCheckKwargsBase):
      feature_check_class = FeatureNew
  
  class FeatureDeprecatedKwargs(FeatureCheckKwargsBase):
      feature_check_class = FeatureDeprecated
  
  
  class InterpreterException(mesonlib.MesonException):
      pass
  
  class InvalidCode(InterpreterException):
      pass
  
  class InvalidArguments(InterpreterException):
      pass
  
  class SubdirDoneRequest(BaseException):
      pass
  
  class InterpreterObject:
      def __init__(self):
          self.methods = {}
  
      def method_call(self, method_name, args, kwargs):
          if method_name in self.methods:
              method = self.methods[method_name]
              if not getattr(method, 'no-args-flattening', False):
                  args = flatten(args)
              return method(args, kwargs)
          raise InvalidCode('Unknown method "%s" in object.' % method_name)
  
  class MutableInterpreterObject(InterpreterObject):
      def __init__(self):
          super().__init__()
  
  class Disabler(InterpreterObject):
      def __init__(self):
          super().__init__()
          self.methods.update({'found': self.found_method})
  
      def found_method(self, args, kwargs):
          return False
  
  def is_disabler(i):
      return isinstance(i, Disabler)
  
  def is_disabled(args, kwargs):
      for i in args:
          if isinstance(i, Disabler):
              return True
      for i in kwargs.values():
          if isinstance(i, Disabler):
              return True
          if isinstance(i, list):
              for j in i:
                  if isinstance(j, Disabler):
                      return True
      return False
  
  class InterpreterBase:
      def __init__(self, source_root, subdir):
          self.source_root = source_root
          self.funcs = {}
          self.builtin = {}
          self.subdir = subdir
          self.variables = {}
          self.argument_depth = 0
          self.current_lineno = -1
  
      def load_root_meson_file(self):
          mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
          if not os.path.isfile(mesonfile):
              raise InvalidArguments('Missing Meson file in %s' % mesonfile)
          with open(mesonfile, encoding='utf8') as mf:
              code = mf.read()
          if code.isspace():
              raise InvalidCode('Builder file is empty.')
          assert(isinstance(code, str))
          try:
              self.ast = mparser.Parser(code, self.subdir).parse()
          except mesonlib.MesonException as me:
              me.file = environment.build_filename
              raise me
  
      def parse_project(self):
          """
          Parses project() and initializes languages, compilers etc. Do this
          early because we need this before we parse the rest of the AST.
          """
          self.evaluate_codeblock(self.ast, end=1)
  
      def sanity_check_ast(self):
          if not isinstance(self.ast, mparser.CodeBlockNode):
              raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
          if not self.ast.lines:
              raise InvalidCode('No statements in code.')
          first = self.ast.lines[0]
          if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
              raise InvalidCode('First statement must be a call to project')
  
      def run(self):
          # Evaluate everything after the first line, which is project() because
          # we already parsed that in self.parse_project()
          try:
              self.evaluate_codeblock(self.ast, start=1)
          except SubdirDoneRequest:
              pass
  
      def evaluate_codeblock(self, node, start=0, end=None):
          if node is None:
              return
          if not isinstance(node, mparser.CodeBlockNode):
              e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
              e.lineno = node.lineno
              e.colno = node.colno
              raise e
          statements = node.lines[start:end]
          i = 0
          while i < len(statements):
              cur = statements[i]
              try:
                  self.current_lineno = cur.lineno
                  self.evaluate_statement(cur)
              except Exception as e:
                  if not(hasattr(e, 'lineno')):
                      e.lineno = cur.lineno
                      e.colno = cur.colno
                      e.file = os.path.join(self.subdir, 'meson.build')
                  raise e
              i += 1 # In THE FUTURE jump over blocks and stuff.
  
      def evaluate_statement(self, cur):
          if isinstance(cur, mparser.FunctionNode):
              return self.function_call(cur)
          elif isinstance(cur, mparser.AssignmentNode):
              return self.assignment(cur)
          elif isinstance(cur, mparser.MethodNode):
              return self.method_call(cur)
          elif isinstance(cur, mparser.StringNode):
              return cur.value
          elif isinstance(cur, mparser.BooleanNode):
              return cur.value
          elif isinstance(cur, mparser.IfClauseNode):
              return self.evaluate_if(cur)
          elif isinstance(cur, mparser.IdNode):
              return self.get_variable(cur.value)
          elif isinstance(cur, mparser.ComparisonNode):
              return self.evaluate_comparison(cur)
          elif isinstance(cur, mparser.ArrayNode):
              return self.evaluate_arraystatement(cur)
          elif isinstance(cur, mparser.DictNode):
              return self.evaluate_dictstatement(cur)
          elif isinstance(cur, mparser.NumberNode):
              return cur.value
          elif isinstance(cur, mparser.AndNode):
              return self.evaluate_andstatement(cur)
          elif isinstance(cur, mparser.OrNode):
              return self.evaluate_orstatement(cur)
          elif isinstance(cur, mparser.NotNode):
              return self.evaluate_notstatement(cur)
          elif isinstance(cur, mparser.UMinusNode):
              return self.evaluate_uminusstatement(cur)
          elif isinstance(cur, mparser.ArithmeticNode):
              return self.evaluate_arithmeticstatement(cur)
          elif isinstance(cur, mparser.ForeachClauseNode):
              return self.evaluate_foreach(cur)
          elif isinstance(cur, mparser.PlusAssignmentNode):
              return self.evaluate_plusassign(cur)
          elif isinstance(cur, mparser.IndexNode):
              return self.evaluate_indexing(cur)
          elif isinstance(cur, mparser.TernaryNode):
              return self.evaluate_ternary(cur)
          elif self.is_elementary_type(cur):
              return cur
          else:
              raise InvalidCode("Unknown statement.")
  
      def evaluate_arraystatement(self, cur):
          (arguments, kwargs) = self.reduce_arguments(cur.args)
          if len(kwargs) > 0:
              raise InvalidCode('Keyword arguments are invalid in array construction.')
          return arguments
  
      @FeatureNew('dict', '0.47.0')
      def evaluate_dictstatement(self, cur):
          (arguments, kwargs) = self.reduce_arguments(cur.args)
          assert (not arguments)
          return kwargs
  
      def evaluate_notstatement(self, cur):
          v = self.evaluate_statement(cur.value)
          if not isinstance(v, bool):
              raise InterpreterException('Argument to "not" is not a boolean.')
          return not v
  
      def evaluate_if(self, node):
          assert(isinstance(node, mparser.IfClauseNode))
          for i in node.ifs:
              result = self.evaluate_statement(i.condition)
              if is_disabler(result):
                  return result
              if not(isinstance(result, bool)):
                  raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
              if result:
                  self.evaluate_codeblock(i.block)
                  return
          if not isinstance(node.elseblock, mparser.EmptyNode):
              self.evaluate_codeblock(node.elseblock)
  
      def validate_comparison_types(self, val1, val2):
          if type(val1) != type(val2):
              return False
          return True
  
      def evaluate_comparison(self, node):
          val1 = self.evaluate_statement(node.left)
          if is_disabler(val1):
              return val1
          val2 = self.evaluate_statement(node.right)
          if is_disabler(val2):
              return val2
          valid = self.validate_comparison_types(val1, val2)
          # Ordering comparisons of different types isn't allowed since PR #1810
          # (0.41.0).  Since PR #2884 we also warn about equality comparisons of
          # different types, which will one day become an error.
          if not valid and (node.ctype == '==' or node.ctype == '!='):
              mlog.warning('''Trying to compare values of different types ({}, {}) using {}.
  The result of this is undefined and will become a hard error in a future Meson release.'''
                           .format(type(val1).__name__, type(val2).__name__, node.ctype), location=node)
          if node.ctype == '==':
              return val1 == val2
          elif node.ctype == '!=':
              return val1 != val2
          elif not valid:
              raise InterpreterException(
                  'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__,
                                                                                           type(val2).__name__,
                                                                                           node.ctype))
          elif not self.is_elementary_type(val1):
              raise InterpreterException('{} can only be compared for equality.'.format(node.left.value))
          elif not self.is_elementary_type(val2):
              raise InterpreterException('{} can only be compared for equality.'.format(node.right.value))
          elif node.ctype == '<':
              return val1 < val2
          elif node.ctype == '<=':
              return val1 <= val2
          elif node.ctype == '>':
              return val1 > val2
          elif node.ctype == '>=':
              return val1 >= val2
          else:
              raise InvalidCode('You broke my compare eval.')
  
      def evaluate_andstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          if not isinstance(l, bool):
              raise InterpreterException('First argument to "and" is not a boolean.')
          if not l:
              return False
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
          if not isinstance(r, bool):
              raise InterpreterException('Second argument to "and" is not a boolean.')
          return r
  
      def evaluate_orstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          if not isinstance(l, bool):
              raise InterpreterException('First argument to "or" is not a boolean.')
          if l:
              return True
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
          if not isinstance(r, bool):
              raise InterpreterException('Second argument to "or" is not a boolean.')
          return r
  
      def evaluate_uminusstatement(self, cur):
          v = self.evaluate_statement(cur.value)
          if is_disabler(v):
              return v
          if not isinstance(v, int):
              raise InterpreterException('Argument to negation is not an integer.')
          return -v
  
      def evaluate_arithmeticstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
  
          if cur.operation == 'add':
              try:
                  return l + r
              except Exception as e:
                  raise InvalidCode('Invalid use of addition: ' + str(e))
          elif cur.operation == 'sub':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Subtraction works only with integers.')
              return l - r
          elif cur.operation == 'mul':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Multiplication works only with integers.')
              return l * r
          elif cur.operation == 'div':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Division works only with integers.')
              return l // r
          elif cur.operation == 'mod':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Modulo works only with integers.')
              return l % r
          else:
              raise InvalidCode('You broke me.')
  
      def evaluate_ternary(self, node):
          assert(isinstance(node, mparser.TernaryNode))
          result = self.evaluate_statement(node.condition)
          if is_disabler(result):
              return result
          if not isinstance(result, bool):
              raise InterpreterException('Ternary condition is not boolean.')
          if result:
              return self.evaluate_statement(node.trueblock)
          else:
              return self.evaluate_statement(node.falseblock)
  
      def evaluate_foreach(self, node):
          assert(isinstance(node, mparser.ForeachClauseNode))
          items = self.evaluate_statement(node.items)
  
          if isinstance(items, list):
              if len(node.varnames)
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/interpreterbase.py
files.old_contents: # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # This class contains the basic functionality needed to run any interpreter
  # or an interpreter-based tool.
  
  from . import mparser, mesonlib, mlog
  from . import environment, dependencies
  
  import os, copy, re, types
  from functools import wraps
  
  # Decorators for method calls.
  
  def check_stringlist(a, msg='Arguments must be strings.'):
      if not isinstance(a, list):
          mlog.debug('Not a list:', str(a))
          raise InvalidArguments('Argument not a list.')
      if not all(isinstance(s, str) for s in a):
          mlog.debug('Element not a string:', str(a))
          raise InvalidArguments(msg)
  
  def _get_callee_args(wrapped_args, want_subproject=False):
      s = wrapped_args[0]
      n = len(wrapped_args)
      # Raise an error if the codepaths are not there
      subproject = None
      if want_subproject and n == 2:
          if hasattr(s, 'subproject'):
              # Interpreter base types have 2 args: self, node
              node_or_state = wrapped_args[1]
              # args and kwargs are inside the node
              args = None
              kwargs = None
              subproject = s.subproject
          elif hasattr(wrapped_args[1], 'subproject'):
              # Module objects have 2 args: self, interpreter
              node_or_state = wrapped_args[1]
              # args and kwargs are inside the node
              args = None
              kwargs = None
              subproject = wrapped_args[1].subproject
          else:
              raise AssertionError('Unknown args: {!r}'.format(wrapped_args))
      elif n == 3:
          # Methods on objects (*Holder, MesonMain, etc) have 3 args: self, args, kwargs
          node_or_state = None # FIXME
          args = wrapped_args[1]
          kwargs = wrapped_args[2]
          if want_subproject:
              if hasattr(s, 'subproject'):
                  subproject = s.subproject
              elif hasattr(s, 'interpreter'):
                  subproject = s.interpreter.subproject
      elif n == 4:
          # Meson functions have 4 args: self, node, args, kwargs
          # Module functions have 4 args: self, state, args, kwargs; except,
          # PythonInstallation methods have self, interpreter, args, kwargs
          node_or_state = wrapped_args[1]
          args = wrapped_args[2]
          kwargs = wrapped_args[3]
          if want_subproject:
              if isinstance(s, InterpreterBase):
                  subproject = s.subproject
              else:
                  subproject = node_or_state.subproject
      elif n == 5:
          # Module snippets have 5 args: self, interpreter, state, args, kwargs
          node_or_state = wrapped_args[2]
          args = wrapped_args[3]
          kwargs = wrapped_args[4]
          if want_subproject:
              subproject = node_or_state.subproject
      else:
          raise AssertionError('Unknown args: {!r}'.format(wrapped_args))
      # Sometimes interpreter methods are called internally with None instead of
      # empty list/dict
      args = args if args is not None else []
      kwargs = kwargs if kwargs is not None else {}
      return s, node_or_state, args, kwargs, subproject
  
  def flatten(args):
      if isinstance(args, mparser.StringNode):
          return args.value
      if isinstance(args, (int, str, mesonlib.File, InterpreterObject)):
          return args
      result = []
      for a in args:
          if isinstance(a, list):
              rest = flatten(a)
              result = result + rest
          elif isinstance(a, mparser.StringNode):
              result.append(a.value)
          else:
              result.append(a)
      return result
  
  def noPosargs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          args = _get_callee_args(wrapped_args)[2]
          if args:
              raise InvalidArguments('Function does not take positional arguments.')
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def noKwargs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          kwargs = _get_callee_args(wrapped_args)[3]
          if kwargs:
              raise InvalidArguments('Function does not take keyword arguments.')
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def stringArgs(f):
      @wraps(f)
      def wrapped(*wrapped_args, **wrapped_kwargs):
          args = _get_callee_args(wrapped_args)[2]
          assert(isinstance(args, list))
          check_stringlist(args)
          return f(*wrapped_args, **wrapped_kwargs)
      return wrapped
  
  def noArgsFlattening(f):
      setattr(f, 'no-args-flattening', True)
      return f
  
  class permittedKwargs:
  
      def __init__(self, permitted):
          self.permitted = permitted
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              s, node_or_state, args, kwargs, _ = _get_callee_args(wrapped_args)
              loc = types.SimpleNamespace()
              if hasattr(s, 'subdir'):
                  loc.subdir = s.subdir
                  loc.lineno = s.current_lineno
              elif node_or_state and hasattr(node_or_state, 'subdir'):
                  loc.subdir = node_or_state.subdir
                  loc.lineno = node_or_state.current_lineno
              else:
                  loc = None
              for k in kwargs:
                  if k not in self.permitted:
                      mlog.warning('''Passed invalid keyword argument "{}".'''.format(k), location=loc)
                      mlog.warning('This will become a hard error in the future.')
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  
  class FeatureCheckBase:
      "Base class for feature version checks"
  
      def __init__(self, feature_name, version):
          self.feature_name = feature_name
          self.feature_version = version
  
      @staticmethod
      def get_target_version(subproject):
          return mesonlib.project_meson_versions[subproject]
  
      def use(self, subproject):
          tv = self.get_target_version(subproject)
          # No target version
          if tv == '':
              return
          # Target version is new enough
          if mesonlib.version_compare_condition_with_min(tv, self.feature_version):
              return
          # Feature is too new for target version, register it
          if subproject not in self.feature_registry:
              self.feature_registry[subproject] = {self.feature_version: set()}
          register = self.feature_registry[subproject]
          if self.feature_version not in register:
              register[self.feature_version] = set()
          if self.feature_name in register[self.feature_version]:
              # Don't warn about the same feature multiple times
              # FIXME: This is needed to prevent duplicate warnings, but also
              # means we won't warn about a feature used in multiple places.
              return
          register[self.feature_version].add(self.feature_name)
          self.log_usage_warning(tv)
  
      @classmethod
      def report(cls, subproject):
          if subproject not in cls.feature_registry:
              return
          warning_str = cls.get_warning_str_prefix(cls.get_target_version(subproject))
          fv = cls.feature_registry[subproject]
          for version in sorted(fv.keys()):
              warning_str += '\n * {}: {}'.format(version, fv[version])
          mlog.warning(warning_str)
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              subproject = _get_callee_args(wrapped_args, want_subproject=True)[4]
              if subproject is None:
                  raise AssertionError('{!r}'.format(wrapped_args))
              self.use(subproject)
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  class FeatureNew(FeatureCheckBase):
      """Checks for new features"""
      # Class variable, shared across all instances
      #
      # Format: {subproject: {feature_version: set(feature_names)}}
      feature_registry = {}
  
      @staticmethod
      def get_warning_str_prefix(tv):
          return 'Project specifies a minimum meson_version \'{}\' which conflicts with:'.format(tv)
  
      def log_usage_warning(self, tv):
          mlog.warning('Project targetting \'{}\' but tried to use feature introduced '
                       'in \'{}\': {}'.format(tv, self.feature_version, self.feature_name))
  
  class FeatureDeprecated(FeatureCheckBase):
      """Checks for deprecated features"""
      # Class variable, shared across all instances
      #
      # Format: {subproject: {feature_version: set(feature_names)}}
      feature_registry = {}
  
      @staticmethod
      def get_warning_str_prefix(tv):
          return 'Deprecated features used:'
  
      def log_usage_warning(self, tv):
          mlog.warning('Project targetting \'{}\' but tried to use feature deprecated '
                       'since \'{}\': {}'.format(tv, self.feature_version, self.feature_name))
  
  
  class FeatureCheckKwargsBase:
      def __init__(self, feature_name, feature_version, kwargs):
          self.feature_name = feature_name
          self.feature_version = feature_version
          self.kwargs = kwargs
  
      def __call__(self, f):
          @wraps(f)
          def wrapped(*wrapped_args, **wrapped_kwargs):
              # Which FeatureCheck class to invoke
              FeatureCheckClass = self.feature_check_class
              kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5]
              if subproject is None:
                  raise AssertionError('{!r}'.format(wrapped_args))
              for arg in self.kwargs:
                  if arg not in kwargs:
                      continue
                  name = arg + ' arg in ' + self.feature_name
                  FeatureCheckClass(name, self.feature_version).use(subproject)
              return f(*wrapped_args, **wrapped_kwargs)
          return wrapped
  
  class FeatureNewKwargs(FeatureCheckKwargsBase):
      feature_check_class = FeatureNew
  
  class FeatureDeprecatedKwargs(FeatureCheckKwargsBase):
      feature_check_class = FeatureDeprecated
  
  
  class InterpreterException(mesonlib.MesonException):
      pass
  
  class InvalidCode(InterpreterException):
      pass
  
  class InvalidArguments(InterpreterException):
      pass
  
  class SubdirDoneRequest(BaseException):
      pass
  
  class InterpreterObject:
      def __init__(self):
          self.methods = {}
  
      def method_call(self, method_name, args, kwargs):
          if method_name in self.methods:
              method = self.methods[method_name]
              if not getattr(method, 'no-args-flattening', False):
                  args = flatten(args)
              return method(args, kwargs)
          raise InvalidCode('Unknown method "%s" in object.' % method_name)
  
  class MutableInterpreterObject(InterpreterObject):
      def __init__(self):
          super().__init__()
  
  class Disabler(InterpreterObject):
      def __init__(self):
          super().__init__()
          self.methods.update({'found': self.found_method})
  
      def found_method(self, args, kwargs):
          return False
  
  def is_disabler(i):
      return isinstance(i, Disabler)
  
  def is_disabled(args, kwargs):
      for i in args:
          if isinstance(i, Disabler):
              return True
      for i in kwargs.values():
          if isinstance(i, Disabler):
              return True
          if isinstance(i, list):
              for j in i:
                  if isinstance(j, Disabler):
                      return True
      return False
  
  class InterpreterBase:
      def __init__(self, source_root, subdir):
          self.source_root = source_root
          self.funcs = {}
          self.builtin = {}
          self.subdir = subdir
          self.variables = {}
          self.argument_depth = 0
          self.current_lineno = -1
  
      def load_root_meson_file(self):
          mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
          if not os.path.isfile(mesonfile):
              raise InvalidArguments('Missing Meson file in %s' % mesonfile)
          with open(mesonfile, encoding='utf8') as mf:
              code = mf.read()
          if code.isspace():
              raise InvalidCode('Builder file is empty.')
          assert(isinstance(code, str))
          try:
              self.ast = mparser.Parser(code, self.subdir).parse()
          except mesonlib.MesonException as me:
              me.file = environment.build_filename
              raise me
  
      def parse_project(self):
          """
          Parses project() and initializes languages, compilers etc. Do this
          early because we need this before we parse the rest of the AST.
          """
          self.evaluate_codeblock(self.ast, end=1)
  
      def sanity_check_ast(self):
          if not isinstance(self.ast, mparser.CodeBlockNode):
              raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
          if not self.ast.lines:
              raise InvalidCode('No statements in code.')
          first = self.ast.lines[0]
          if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
              raise InvalidCode('First statement must be a call to project')
  
      def run(self):
          # Evaluate everything after the first line, which is project() because
          # we already parsed that in self.parse_project()
          try:
              self.evaluate_codeblock(self.ast, start=1)
          except SubdirDoneRequest:
              pass
  
      def evaluate_codeblock(self, node, start=0, end=None):
          if node is None:
              return
          if not isinstance(node, mparser.CodeBlockNode):
              e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
              e.lineno = node.lineno
              e.colno = node.colno
              raise e
          statements = node.lines[start:end]
          i = 0
          while i < len(statements):
              cur = statements[i]
              try:
                  self.current_lineno = cur.lineno
                  self.evaluate_statement(cur)
              except Exception as e:
                  if not(hasattr(e, 'lineno')):
                      e.lineno = cur.lineno
                      e.colno = cur.colno
                      e.file = os.path.join(self.subdir, 'meson.build')
                  raise e
              i += 1 # In THE FUTURE jump over blocks and stuff.
  
      def evaluate_statement(self, cur):
          if isinstance(cur, mparser.FunctionNode):
              return self.function_call(cur)
          elif isinstance(cur, mparser.AssignmentNode):
              return self.assignment(cur)
          elif isinstance(cur, mparser.MethodNode):
              return self.method_call(cur)
          elif isinstance(cur, mparser.StringNode):
              return cur.value
          elif isinstance(cur, mparser.BooleanNode):
              return cur.value
          elif isinstance(cur, mparser.IfClauseNode):
              return self.evaluate_if(cur)
          elif isinstance(cur, mparser.IdNode):
              return self.get_variable(cur.value)
          elif isinstance(cur, mparser.ComparisonNode):
              return self.evaluate_comparison(cur)
          elif isinstance(cur, mparser.ArrayNode):
              return self.evaluate_arraystatement(cur)
          elif isinstance(cur, mparser.DictNode):
              return self.evaluate_dictstatement(cur)
          elif isinstance(cur, mparser.NumberNode):
              return cur.value
          elif isinstance(cur, mparser.AndNode):
              return self.evaluate_andstatement(cur)
          elif isinstance(cur, mparser.OrNode):
              return self.evaluate_orstatement(cur)
          elif isinstance(cur, mparser.NotNode):
              return self.evaluate_notstatement(cur)
          elif isinstance(cur, mparser.UMinusNode):
              return self.evaluate_uminusstatement(cur)
          elif isinstance(cur, mparser.ArithmeticNode):
              return self.evaluate_arithmeticstatement(cur)
          elif isinstance(cur, mparser.ForeachClauseNode):
              return self.evaluate_foreach(cur)
          elif isinstance(cur, mparser.PlusAssignmentNode):
              return self.evaluate_plusassign(cur)
          elif isinstance(cur, mparser.IndexNode):
              return self.evaluate_indexing(cur)
          elif isinstance(cur, mparser.TernaryNode):
              return self.evaluate_ternary(cur)
          elif self.is_elementary_type(cur):
              return cur
          else:
              raise InvalidCode("Unknown statement.")
  
      def evaluate_arraystatement(self, cur):
          (arguments, kwargs) = self.reduce_arguments(cur.args)
          if len(kwargs) > 0:
              raise InvalidCode('Keyword arguments are invalid in array construction.')
          return arguments
  
      @FeatureNew('dict', '0.47.0')
      def evaluate_dictstatement(self, cur):
          (arguments, kwargs) = self.reduce_arguments(cur.args)
          assert (not arguments)
          return kwargs
  
      def evaluate_notstatement(self, cur):
          v = self.evaluate_statement(cur.value)
          if not isinstance(v, bool):
              raise InterpreterException('Argument to "not" is not a boolean.')
          return not v
  
      def evaluate_if(self, node):
          assert(isinstance(node, mparser.IfClauseNode))
          for i in node.ifs:
              result = self.evaluate_statement(i.condition)
              if is_disabler(result):
                  return result
              if not(isinstance(result, bool)):
                  raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
              if result:
                  self.evaluate_codeblock(i.block)
                  return
          if not isinstance(node.elseblock, mparser.EmptyNode):
              self.evaluate_codeblock(node.elseblock)
  
      def validate_comparison_types(self, val1, val2):
          if type(val1) != type(val2):
              return False
          return True
  
      def evaluate_comparison(self, node):
          val1 = self.evaluate_statement(node.left)
          if is_disabler(val1):
              return val1
          val2 = self.evaluate_statement(node.right)
          if is_disabler(val2):
              return val2
          valid = self.validate_comparison_types(val1, val2)
          # Ordering comparisons of different types isn't allowed since PR #1810
          # (0.41.0).  Since PR #2884 we also warn about equality comparisons of
          # different types, which will one day become an error.
          if not valid and (node.ctype == '==' or node.ctype == '!='):
              mlog.warning('''Trying to compare values of different types ({}, {}) using {}.
  The result of this is undefined and will become a hard error in a future Meson release.'''
                           .format(type(val1).__name__, type(val2).__name__, node.ctype), location=node)
          if node.ctype == '==':
              return val1 == val2
          elif node.ctype == '!=':
              return val1 != val2
          elif not valid:
              raise InterpreterException(
                  'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__,
                                                                                           type(val2).__name__,
                                                                                           node.ctype))
          elif not self.is_elementary_type(val1):
              raise InterpreterException('{} can only be compared for equality.'.format(node.left.value))
          elif not self.is_elementary_type(val2):
              raise InterpreterException('{} can only be compared for equality.'.format(node.right.value))
          elif node.ctype == '<':
              return val1 < val2
          elif node.ctype == '<=':
              return val1 <= val2
          elif node.ctype == '>':
              return val1 > val2
          elif node.ctype == '>=':
              return val1 >= val2
          else:
              raise InvalidCode('You broke my compare eval.')
  
      def evaluate_andstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          if not isinstance(l, bool):
              raise InterpreterException('First argument to "and" is not a boolean.')
          if not l:
              return False
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
          if not isinstance(r, bool):
              raise InterpreterException('Second argument to "and" is not a boolean.')
          return r
  
      def evaluate_orstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          if not isinstance(l, bool):
              raise InterpreterException('First argument to "or" is not a boolean.')
          if l:
              return True
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
          if not isinstance(r, bool):
              raise InterpreterException('Second argument to "or" is not a boolean.')
          return r
  
      def evaluate_uminusstatement(self, cur):
          v = self.evaluate_statement(cur.value)
          if is_disabler(v):
              return v
          if not isinstance(v, int):
              raise InterpreterException('Argument to negation is not an integer.')
          return -v
  
      def evaluate_arithmeticstatement(self, cur):
          l = self.evaluate_statement(cur.left)
          if is_disabler(l):
              return l
          r = self.evaluate_statement(cur.right)
          if is_disabler(r):
              return r
  
          if cur.operation == 'add':
              try:
                  return l + r
              except Exception as e:
                  raise InvalidCode('Invalid use of addition: ' + str(e))
          elif cur.operation == 'sub':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Subtraction works only with integers.')
              return l - r
          elif cur.operation == 'mul':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Multiplication works only with integers.')
              return l * r
          elif cur.operation == 'div':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Division works only with integers.')
              return l // r
          elif cur.operation == 'mod':
              if not isinstance(l, int) or not isinstance(r, int):
                  raise InvalidCode('Modulo works only with integers.')
              return l % r
          else:
              raise InvalidCode('You broke me.')
  
      def evaluate_ternary(self, node):
          assert(isinstance(node, mparser.TernaryNode))
          result = self.evaluate_statement(node.condition)
          if is_disabler(result):
              return result
          if not isinstance(result, bool):
              raise InterpreterException('Ternary condition is not boolean.')
          if result:
              return self.evaluate_statement(node.trueblock)
          else:
              return self.evaluate_statement(node.falseblock)
  
      def evaluate_foreach(self, node):
          assert(isinstance(node, mparser.ForeachClauseNode))
          items = self.evaluate_statement(node.items)
  
          if isinstance(items, list):
              if len(node.varnames) != 1:
                 
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/interpreterbase.py
files.new_contents: # Copyright 2013-2014 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os
  import io
  import sys
  import time
  import platform
  from contextlib import contextmanager
  
  """This is (mostly) a standalone module used to write logging
  information about Meson runs. Some output goes to screen,
  some to logging dir and some goes to both."""
  
  def _windows_ansi():
      from ctypes import windll, byref
      from ctypes.wintypes import DWORD
  
      kernel = windll.kernel32
      stdout = kernel.GetStdHandle(-11)
      mode = DWORD()
      if not kernel.GetConsoleMode(stdout, byref(mode)):
          return False
      # ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0x4
      # If the call to enable VT processing fails (returns 0), we fallback to
      # original behavior
      return kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')
  
  if platform.system().lower() == 'windows':
      colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi()
  else:
      colorize_console = os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb'
  log_dir = None
  log_file = None
  log_fname = 'meson-log.txt'
  log_depth = 0
  log_timestamp_start = None
  
  def initialize(logdir):
      global log_dir, log_file
      log_dir = logdir
      log_file = open(os.path.join(logdir, log_fname), 'w', encoding='utf8')
  
  def set_timestamp_start(start):
      global log_timestamp_start
      log_timestamp_start = start
  
  def shutdown():
      global log_file
      if log_file is not None:
          exception_around_goer = log_file
          log_file = None
          exception_around_goer.close()
  
  class AnsiDecorator:
      plain_code = "\033[0m"
  
      def __init__(self, text, code, quoted=False):
          self.text = text
          self.code = code
          self.quoted = quoted
  
      def get_text(self, with_codes):
          text = self.text
          if with_codes:
              text = self.code + self.text + AnsiDecorator.plain_code
          if self.quoted:
              text = '"{}"'.format(text)
          return text
  
  def bold(text, quoted=False):
      return AnsiDecorator(text, "\033[1m", quoted=quoted)
  
  def red(text):
      return AnsiDecorator(text, "\033[1;31m")
  
  def green(text):
      return AnsiDecorator(text, "\033[1;32m")
  
  def yellow(text):
      return AnsiDecorator(text, "\033[1;33m")
  
  def cyan(text):
      return AnsiDecorator(text, "\033[1;36m")
  
  def process_markup(args, keep):
      arr = []
      if log_timestamp_start is not None:
          arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)]
      for arg in args:
          if isinstance(arg, str):
              arr.append(arg)
          elif isinstance(arg, AnsiDecorator):
              arr.append(arg.get_text(keep))
          else:
              arr.append(str(arg))
      return arr
  
  def force_print(*args, **kwargs):
      iostr = io.StringIO()
      kwargs['file'] = iostr
      print(*args, **kwargs)
  
      raw = iostr.getvalue()
      if log_depth > 0:
          prepend = '|' * log_depth
          raw = prepend + raw.replace('\n', '\n' + prepend, raw.count('\n') - 1)
  
      # _Something_ is going to get printed.
      try:
          print(raw, end='')
      except UnicodeEncodeError:
          cleaned = raw.encode('ascii', 'replace').decode('ascii')
          print(cleaned, end='')
  
  def debug(*args, **kwargs):
      arr = process_markup(args, False)
      if log_file is not None:
          print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
          log_file.flush()
  
  def log(*args, **kwargs):
      arr = process_markup(args, False)
      if log_file is not None:
          print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
          log_file.flush()
      if colorize_console:
          arr = process_markup(args, True)
      force_print(*arr, **kwargs)
  
  def _log_error(severity, *args, **kwargs):
      from . import environment
      if severity == 'warning':
          args = (yellow('WARNING:'),) + args
      elif severity == 'error':
          args = (red('ERROR:'),) + args
      elif severity == 'deprecation':
          args = (red('DEPRECATION:'),) + args
      else:
          assert False, 'Invalid severity ' + severity
  
      location = kwargs.pop('location', None)
      if location is not None:
          location_str = '{}:{}:'.format(os.path.join(location.subdir,
                                                      environment.build_filename),
                                         location.lineno)
          args = (location_str,) + args
  
      log(*args, **kwargs)
  
  def error(*args, **kwargs):
      return _log_error('error', *args, **kwargs)
  
  def warning(*args, **kwargs):
      return _log_error('warning', *args, **kwargs)
  
  def deprecation(*args, **kwargs):
      return _log_error('deprecation', *args, **kwargs)
  
  def exception(e):
      log()
      if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'):
          log('%s:%d:%d:' % (e.file, e.lineno, e.colno), red('ERROR: '), e)
      else:
          log(red('ERROR:'), e)
  
  # Format a list for logging purposes as a string. It separates
  # all but the last item with commas, and the last with 'and'.
  def format_list(list):
      l = len(list)
      if l > 2:
          return ' and '.join([', '.join(list[:-1]), list[-1]])
      elif l == 2:
          return ' and '.join(list)
      elif l == 1:
          return list[0]
      else:
          return ''
  
  @contextmanager
  def nested():
      global log_depth
      log_depth += 1
      try:
          yield
      finally:
          log_depth -= 1
  
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/mlog.py
files.old_contents: # Copyright 2013-2014 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os
  import io
  import sys
  import time
  import platform
  from contextlib import contextmanager
  
  """This is (mostly) a standalone module used to write logging
  information about Meson runs. Some output goes to screen,
  some to logging dir and some goes to both."""
  
  def _windows_ansi():
      from ctypes import windll, byref
      from ctypes.wintypes import DWORD
  
      kernel = windll.kernel32
      stdout = kernel.GetStdHandle(-11)
      mode = DWORD()
      if not kernel.GetConsoleMode(stdout, byref(mode)):
          return False
      # ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0x4
      # If the call to enable VT processing fails (returns 0), we fallback to
      # original behavior
      return kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')
  
  if platform.system().lower() == 'windows':
      colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi()
  else:
      colorize_console = os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb'
  log_dir = None
  log_file = None
  log_fname = 'meson-log.txt'
  log_depth = 0
  log_timestamp_start = None
  
  def initialize(logdir):
      global log_dir, log_file
      log_dir = logdir
      log_file = open(os.path.join(logdir, log_fname), 'w', encoding='utf8')
  
  def set_timestamp_start(start):
      global log_timestamp_start
      log_timestamp_start = start
  
  def shutdown():
      global log_file
      if log_file is not None:
          exception_around_goer = log_file
          log_file = None
          exception_around_goer.close()
  
  class AnsiDecorator:
      plain_code = "\033[0m"
  
      def __init__(self, text, code, quoted=False):
          self.text = text
          self.code = code
          self.quoted = quoted
  
      def get_text(self, with_codes):
          text = self.text
          if with_codes:
              text = self.code + self.text + AnsiDecorator.plain_code
          if self.quoted:
              text = '"{}"'.format(text)
          return text
  
  def bold(text, quoted=False):
      return AnsiDecorator(text, "\033[1m", quoted=quoted)
  
  def red(text):
      return AnsiDecorator(text, "\033[1;31m")
  
  def green(text):
      return AnsiDecorator(text, "\033[1;32m")
  
  def yellow(text):
      return AnsiDecorator(text, "\033[1;33m")
  
  def cyan(text):
      return AnsiDecorator(text, "\033[1;36m")
  
  def process_markup(args, keep):
      arr = []
      if log_timestamp_start is not None:
          arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)]
      for arg in args:
          if isinstance(arg, str):
              arr.append(arg)
          elif isinstance(arg, AnsiDecorator):
              arr.append(arg.get_text(keep))
          else:
              arr.append(str(arg))
      return arr
  
  def force_print(*args, **kwargs):
      iostr = io.StringIO()
      kwargs['file'] = iostr
      print(*args, **kwargs)
  
      raw = iostr.getvalue()
      if log_depth > 0:
          prepend = '|' * log_depth
          raw = prepend + raw.replace('\n', '\n' + prepend, raw.count('\n') - 1)
  
      # _Something_ is going to get printed.
      try:
          print(raw, end='')
      except UnicodeEncodeError:
          cleaned = raw.encode('ascii', 'replace').decode('ascii')
          print(cleaned, end='')
  
  def debug(*args, **kwargs):
      arr = process_markup(args, False)
      if log_file is not None:
          print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
          log_file.flush()
  
  def log(*args, **kwargs):
      arr = process_markup(args, False)
      if log_file is not None:
          print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes.
          log_file.flush()
      if colorize_console:
          arr = process_markup(args, True)
      force_print(*arr, **kwargs)
  
  def _log_error(severity, *args, **kwargs):
      from . import environment
      if severity == 'warning':
          args = (yellow('WARNING:'),) + args
      elif severity == 'error':
          args = (red('ERROR:'),) + args
      else:
          assert False, 'Invalid severity ' + severity
  
      location = kwargs.pop('location', None)
      if location is not None:
          location_str = '{}:{}:'.format(os.path.join(location.subdir,
                                                      environment.build_filename),
                                         location.lineno)
          args = (location_str,) + args
  
      log(*args, **kwargs)
  
  def error(*args, **kwargs):
      return _log_error('error', *args, **kwargs)
  
  def warning(*args, **kwargs):
      return _log_error('warning', *args, **kwargs)
  
  def exception(e):
      log()
      if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'):
          log('%s:%d:%d:' % (e.file, e.lineno, e.colno), red('ERROR: '), e)
      else:
          log(red('ERROR:'), e)
  
  # Format a list for logging purposes as a string. It separates
  # all but the last item with commas, and the last with 'and'.
  def format_list(list):
      l = len(list)
      if l > 2:
          return ' and '.join([', '.join(list[:-1]), list[-1]])
      elif l == 2:
          return ' and '.join(list)
      elif l == 1:
          return list[0]
      else:
          return ''
  
  @contextmanager
  def nested():
      global log_depth
      log_depth += 1
      try:
          yield
      finally:
          log_depth -= 1
  
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/mlog.py
files.new_contents: # Copyright 2015-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  '''This module provides helper functions for Gnome/GLib related
  functionality such as gobject-introspection, gresources and gtk-doc'''
  
  import os
  import copy
  import subprocess
  
  from .. import build
  from .. import mlog
  from .. import mesonlib
  from .. import compilers
  from .. import interpreter
  from . import GResourceTarget, GResourceHeaderTarget, GirTarget, TypelibTarget, VapiTarget
  from . import get_include_args
  from . import ExtensionModule
  from . import ModuleReturnValue
  from ..mesonlib import MesonException, OrderedSet, Popen_safe, extract_as_list
  from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
  from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs
  
  # gresource compilation is broken due to the way
  # the resource compiler and Ninja clash about it
  #
  # https://github.com/ninja-build/ninja/issues/1184
  # https://bugzilla.gnome.org/show_bug.cgi?id=774368
  gresource_dep_needed_version = '>= 2.51.1'
  
  native_glib_version = None
  girwarning_printed = False
  gdbuswarning_printed = False
  gresource_warning_printed = False
  _gir_has_extra_lib_arg = None
  
  def gir_has_extra_lib_arg(intr_obj):
      global _gir_has_extra_lib_arg
      if _gir_has_extra_lib_arg is not None:
          return _gir_has_extra_lib_arg
  
      _gir_has_extra_lib_arg = False
      try:
          g_ir_scanner = intr_obj.find_program_impl('g-ir-scanner').get_command()
          opts = Popen_safe(g_ir_scanner + ['--help'], stderr=subprocess.STDOUT)[1]
          _gir_has_extra_lib_arg = '--extra-library' in opts
      except (MesonException, FileNotFoundError, subprocess.CalledProcessError):
          pass
      return _gir_has_extra_lib_arg
  
  class GnomeModule(ExtensionModule):
      gir_dep = None
  
      @staticmethod
      def _get_native_glib_version(state):
          global native_glib_version
          if native_glib_version is None:
              glib_dep = PkgConfigDependency('glib-2.0', state.environment,
                                             {'native': True, 'required': False})
              if glib_dep.found():
                  native_glib_version = glib_dep.get_version()
              else:
                  mlog.warning('Could not detect glib version, assuming 2.54. '
                               'You may get build errors if your glib is older.')
                  native_glib_version = '2.54'
          return native_glib_version
  
      def __print_gresources_warning(self, state):
          global gresource_warning_printed
          if not gresource_warning_printed:
              if not mesonlib.version_compare(self._get_native_glib_version(state), gresource_dep_needed_version):
                  mlog.warning('GLib compiled dependencies do not work reliably with \n'
                               'the current version of GLib. See the following upstream issue:',
                               mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368'))
              gresource_warning_printed = True
          return []
  
      @staticmethod
      def _print_gdbus_warning():
          global gdbuswarning_printed
          if not gdbuswarning_printed:
              mlog.warning('Code generated with gdbus_codegen() requires the root directory be added to\n'
                           '  include_directories of targets with GLib < 2.51.3:',
                           mlog.bold('https://github.com/mesonbuild/meson/issues/1387'))
              gdbuswarning_printed = True
  
      @FeatureNewKwargs('gnome.compile_resources', '0.37.0', ['gresource_bundle', 'export', 'install_header'])
      @permittedKwargs({'source_dir', 'c_name', 'dependencies', 'export', 'gresource_bundle', 'install_header',
                        'install', 'install_dir', 'extra_args', 'build_by_default'})
      def compile_resources(self, state, args, kwargs):
          self.__print_gresources_warning(state)
          glib_version = self._get_native_glib_version(state)
  
          cmd = ['glib-compile-resources', '@INPUT@']
  
          source_dirs, dependencies = mesonlib.extract_as_list(kwargs, 'source_dir', 'dependencies', pop=True)
  
          if len(args) < 2:
              raise MesonException('Not enough arguments; the name of the resource '
                                   'and the path to the XML file are required')
  
          # Validate dependencies
          for (ii, dep) in enumerate(dependencies):
              if hasattr(dep, 'held_object'):
                  dependencies[ii] = dep = dep.held_object
              if not isinstance(dep, (mesonlib.File, build.CustomTarget, build.CustomTargetIndex)):
                  m = 'Unexpected dependency type {!r} for gnome.compile_resources() ' \
                      '"dependencies" argument.\nPlease pass the return value of ' \
                      'custom_target() or configure_file()'
                  raise MesonException(m.format(dep))
              if isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)):
                  if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
                      m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \
                          'be used with the current version of glib-compile-resources due to\n' \
                          '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>'
                      raise MesonException(m)
  
          ifile = args[1]
          if isinstance(ifile, mesonlib.File):
              # glib-compile-resources will be run inside the source dir,
              # so we need either 'src_to_build' or the absolute path.
              # Absolute path is the easiest choice.
              if ifile.is_built:
                  ifile = os.path.join(state.environment.get_build_dir(), ifile.subdir, ifile.fname)
              else:
                  ifile = os.path.join(ifile.subdir, ifile.fname)
          elif isinstance(ifile, str):
              ifile = os.path.join(state.subdir, ifile)
          elif isinstance(ifile, (interpreter.CustomTargetHolder,
                                  interpreter.CustomTargetIndexHolder,
                                  interpreter.GeneratedObjectsHolder)):
              m = 'Resource xml files generated at build-time cannot be used ' \
                  'with gnome.compile_resources() because we need to scan ' \
                  'the xml for dependencies. Use configure_file() instead ' \
                  'to generate it at configure-time.'
              raise MesonException(m)
          else:
              raise MesonException('Invalid file argument: {!r}'.format(ifile))
  
          depend_files, depends, subdirs = self._get_gresource_dependencies(
              state, ifile, source_dirs, dependencies)
  
          # Make source dirs relative to build dir now
          source_dirs = [os.path.join(state.build_to_src, state.subdir, d) for d in source_dirs]
          # Always include current directory, but after paths set by user
          source_dirs.append(os.path.join(state.build_to_src, state.subdir))
          # Ensure build directories of generated deps are included
          source_dirs += subdirs
  
          for source_dir in OrderedSet(source_dirs):
              cmd += ['--sourcedir', source_dir]
  
          if 'c_name' in kwargs:
              cmd += ['--c-name', kwargs.pop('c_name')]
          export = kwargs.pop('export', False)
          if not export:
              cmd += ['--internal']
  
          cmd += ['--generate', '--target', '@OUTPUT@']
  
          cmd += mesonlib.stringlistify(kwargs.pop('extra_args', []))
  
          gresource = kwargs.pop('gresource_bundle', False)
          if gresource:
              output = args[0] + '.gresource'
              name = args[0] + '_gresource'
          else:
              output = args[0] + '.c'
              name = args[0] + '_c'
  
          if kwargs.get('install', False) and not gresource:
              raise MesonException('The install kwarg only applies to gresource bundles, see install_header')
  
          install_header = kwargs.pop('install_header', False)
          if install_header and gresource:
              raise MesonException('The install_header kwarg does not apply to gresource bundles')
          if install_header and not export:
              raise MesonException('GResource header is installed yet export is not enabled')
  
          kwargs['input'] = args[1]
          kwargs['output'] = output
          kwargs['depends'] = depends
          if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
              # This will eventually go out of sync if dependencies are added
              kwargs['depend_files'] = depend_files
              kwargs['command'] = cmd
          else:
              depfile = kwargs['output'] + '.d'
              kwargs['depfile'] = depfile
              kwargs['command'] = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@']
          target_c = GResourceTarget(name, state.subdir, state.subproject, kwargs)
  
          if gresource: # Only one target for .gresource files
              return ModuleReturnValue(target_c, [target_c])
  
          h_kwargs = {
              'command': cmd,
              'input': args[1],
              'output': args[0] + '.h',
              # The header doesn't actually care about the files yet it errors if missing
              'depends': depends
          }
          if 'build_by_default' in kwargs:
              h_kwargs['build_by_default'] = kwargs['build_by_default']
          if install_header:
              h_kwargs['install'] = install_header
              h_kwargs['install_dir'] = kwargs.get('install_dir',
                                                   state.environment.coredata.get_builtin_option('includedir'))
          target_h = GResourceHeaderTarget(args[0] + '_h', state.subdir, state.subproject, h_kwargs)
          rv = [target_c, target_h]
          return ModuleReturnValue(rv, rv)
  
      def _get_gresource_dependencies(self, state, input_file, source_dirs, dependencies):
  
          cmd = ['glib-compile-resources',
                 input_file,
                 '--generate-dependencies']
  
          # Prefer generated files over source files
          cmd += ['--sourcedir', state.subdir] # Current build dir
          for source_dir in source_dirs:
              cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)]
  
          pc, stdout, stderr = Popen_safe(cmd, cwd=state.environment.get_source_dir())
          if pc.returncode != 0:
              m = 'glib-compile-resources failed to get dependencies for {}:\n{}'
              mlog.warning(m.format(cmd[1], stderr))
              raise subprocess.CalledProcessError(pc.returncode, cmd)
  
          dep_files = stdout.split('\n')[:-1]
  
          depends = []
          subdirs = []
          for resfile in dep_files[:]:
              resbasename = os.path.basename(resfile)
              for dep in dependencies:
                  if hasattr(dep, 'held_object'):
                      dep = dep.held_object
                  if isinstance(dep, mesonlib.File):
                      if dep.fname != resbasename:
                          continue
                      dep_files.remove(resfile)
                      dep_files.append(dep)
                      subdirs.append(dep.subdir)
                      break
                  elif isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)):
                      fname = None
                      outputs = {(o, os.path.basename(o)) for o in dep.get_outputs()}
                      for o, baseo in outputs:
                          if baseo == resbasename:
                              fname = o
                              break
                      if fname is not None:
                          dep_files.remove(resfile)
                          depends.append(dep)
                          subdirs.append(dep.get_subdir())
                          break
              else:
                  # In generate-dependencies mode, glib-compile-resources doesn't raise
                  # an error for missing resources but instead prints whatever filename
                  # was listed in the input file.  That's good because it means we can
                  # handle resource files that get generated as part of the build, as
                  # follows.
                  #
                  # If there are multiple generated resource files with the same basename
                  # then this code will get confused.
                  try:
                      f = mesonlib.File.from_source_file(state.environment.get_source_dir(),
                                                         ".", resfile)
                  except MesonException:
                      raise MesonException(
                          'Resource "%s" listed in "%s" was not found. If this is a '
                          'generated file, pass the target that generates it to '
                          'gnome.compile_resources() using the "dependencies" '
                          'keyword argument.' % (resfile, input_file))
                  dep_files.remove(resfile)
                  dep_files.append(f)
          return dep_files, depends, subdirs
  
      def _get_link_args(self, state, lib, depends, include_rpath=False,
                         use_gir_args=False):
          link_command = []
          # Construct link args
          if isinstance(lib, build.SharedLibrary):
              libdir = os.path.join(state.environment.get_build_dir(), state.backend.get_target_dir(lib))
              link_command.append('-L' + libdir)
              # Needed for the following binutils bug:
              # https://github.com/mesonbuild/meson/issues/1911
              # However, g-ir-scanner does not understand -Wl,-rpath
              # so we need to use -L instead
              for d in state.backend.determine_rpath_dirs(lib):
                  d = os.path.join(state.environment.get_build_dir(), d)
                  link_command.append('-L' + d)
                  if include_rpath:
                      link_command.append('-Wl,-rpath,' + d)
              if include_rpath:
                  link_command.append('-Wl,-rpath,' + libdir)
              depends.append(lib)
          if gir_has_extra_lib_arg(self.interpreter) and use_gir_args:
              link_command.append('--extra-library=' + lib.name)
          else:
              link_command.append('-l' + lib.name)
          return link_command
  
      def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
                                  use_gir_args=False):
          cflags = OrderedSet()
          internal_ldflags = OrderedSet()
          external_ldflags = OrderedSet()
          gi_includes = OrderedSet()
          deps = mesonlib.listify(deps, unholder=True)
  
          for dep in deps:
              if isinstance(dep, InternalDependency):
                  cflags.update(get_include_args(dep.include_directories))
                  for lib in dep.libraries:
                      if hasattr(lib, 'held_object'):
                          lib = lib.held_object
                      internal_ldflags.update(self._get_link_args(state, lib, depends, include_rpath))
                      libdepflags = self._get_dependencies_flags(lib.get_external_deps(), state, depends, include_rpath,
                                                                 use_gir_args)
                      cflags.update(libdepflags[0])
                      internal_ldflags.update(libdepflags[1])
                      external_ldflags.update(libdepflags[2])
                      gi_includes.update(libdepflags[3])
                  extdepflags = self._get_dependencies_flags(dep.ext_deps, state, depends, include_rpath,
                                                             use_gir_args)
                  cflags.update(extdepflags[0])
                  internal_ldflags.update(extdepflags[1])
                  external_ldflags.update(extdepflags[2])
                  gi_includes.update(extdepflags[3])
                  for source in dep.sources:
                      if hasattr(source, 'held_object'):
                          source = source.held_object
                      if isinstance(source, GirTarget):
                          gi_includes.update([os.path.join(state.environment.get_build_dir(),
                                              source.get_subdir())])
              # This should be any dependency other than an internal one.
              elif isinstance(dep, Dependency):
                  cflags.update(dep.get_compile_args())
                  for lib in dep.get_link_args(raw=True):
                      if (os.path.isabs(lib) and
                              # For PkgConfigDependency only:
                              getattr(dep, 'is_libtool', False)):
                          lib_dir = os.path.dirname(lib)
                          external_ldflags.update(["-L%s" % lib_dir])
                          if include_rpath:
                              external_ldflags.update(['-Wl,-rpath {}'.format(lib_dir)])
                          libname = os.path.basename(lib)
                          if libname.startswith("lib"):
                              libname = libname[3:]
                          libname = libname.split(".so")[0]
                          lib = "-l%s" % libname
                      # Hack to avoid passing some compiler options in
                      if lib.startswith("-W"):
                          continue
                      external_ldflags.update([lib])
  
                  if isinstance(dep, PkgConfigDependency):
                      girdir = dep.get_pkgconfig_variable("girdir", {'default': ''})
                      if girdir:
                          gi_includes.update([girdir])
              elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
                  cflags.update(get_include_args(dep.get_include_dirs()))
                  depends.append(dep)
              else:
                  mlog.log('dependency {!r} not handled to build gir files'.format(dep))
                  continue
  
          if gir_has_extra_lib_arg(self.interpreter) and use_gir_args:
              def fix_ldflags(ldflags):
                  fixed_ldflags = OrderedSet()
                  for ldflag in ldflags:
                      if ldflag.startswith("-l"):
                          fixed_ldflags.add(ldflag.replace('-l', '--extra-library=', 1))
                      else:
                          fixed_ldflags.add(ldflag)
                  return fixed_ldflags
              internal_ldflags = fix_ldflags(internal_ldflags)
              external_ldflags = fix_ldflags(external_ldflags)
          return cflags, internal_ldflags, external_ldflags, gi_includes
  
      @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
      @permittedKwargs({'sources', 'nsversion', 'namespace', 'symbol_prefix', 'identifier_prefix',
                        'export_packages', 'includes', 'dependencies', 'link_with', 'include_directories',
                        'install', 'install_dir_gir', 'install_dir_typelib', 'extra_args',
                        'packages', 'header', 'build_by_default'})
      def generate_gir(self, state, args, kwargs):
          if len(args) != 1:
              raise MesonException('Gir takes one argument')
          if kwargs.get('install_dir'):
              raise MesonException('install_dir is not supported with generate_gir(), see "install_dir_gir" and "install_dir_typelib"')
          giscanner = self.interpreter.find_program_impl('g-ir-scanner')
          gicompiler = self.interpreter.find_program_impl('g-ir-compiler')
          girtarget = args[0]
          while hasattr(girtarget, 'held_object'):
              girtarget = girtarget.held_object
          if not isinstance(girtarget, (build.Executable, build.SharedLibrary)):
              raise MesonException('Gir target must be an executable or shared library')
          try:
              if not self.gir_dep:
                  self.gir_dep = PkgConfigDependency('gobject-introspection-1.0',
                                                     state.environment,
                                                     {'native': True})
              pkgargs = self.gir_dep.get_compile_args()
          except Exception:
              raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.')
          ns = kwargs.pop('namespace')
          nsversion = kwargs.pop('nsversion')
          libsources = mesonlib.extract_as_list(kwargs, 'sources', pop=True)
          girfile = '%s-%s.gir' % (ns, nsversion)
          srcdir = os.path.join(state.environment.get_source_dir(), state.subdir)
          builddir = os.path.join(state.environment.get_build_dir(), state.subdir)
          depends = [girtarget]
          gir_inc_dirs = []
  
          scan_command = [giscanner]
          scan_command += pkgargs
          scan_command += ['--no-libtool', '--namespace=' + ns, '--nsversion=' + nsversion, '--warn-all',
                           '--output', '@OUTPUT@']
  
          header = kwargs.pop('header', None)
          if header:
              if not isinstance(header, str):
                  raise MesonException('header must be a string')
              scan_command += ['--c-include=' + header]
  
          extra_args = mesonlib.stringlistify(kwargs.pop('extra_args', []))
          scan_command += extra_args
          scan_command += ['-I' + srcdir,
                           '-I' + builddir]
          scan_command += get_include_args(girtarget.get_include_dirs())
  
          gir_filelist_dir = state.backend.get_target_private_dir_abs(girtarget)
          if not os.path.isdir(gir_filelist_dir):
              os.mkdir(gir_filelist_dir)
          gir_filelist_filename = os.path.join(gir_filelist_dir, '%s_%s_gir_filelist' % (ns, nsversion))
  
          with open(gir_filelist_filename, 'w', encoding='utf-8') as gir_filelist:
              for s in libsources:
                  if hasattr(s, 'held_object'):
                      s = s.held_object
                  if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)):
                      for custom_output in s.get_outputs():
                          gir_filelist.write(os.path.join(state.environment.get_build_dir(),
                                                          state.backend.get_target_dir(s),
                                                          custom_output) + '\n')
                  elif isinstance(s, mesonlib.File):
                      gir_filelist.write(s.rel_to_builddir(state.build_to_src) + '\n')
                  elif isinstance(s, build.GeneratedList):
                      for gen_src in s.get_outputs():
                          gir_filelist.write(os.path.join(srcdir, gen_src) + '\n')
                  else:
                      gir_filelist.write(os.path.join(srcdir, s) + '\n')
          scan_command += ['--filelist=' + gir_filelist_filename]
  
          if 'link_with' in kwargs:
              link_with = mesonlib.extract_as_list(kwargs, 'link_with', pop = True)
  
              for link in link_with:
                  scan_command += self._get_link_args(state, link.held_object, depends,
                                                      use_gir_args=True)
  
          if 'includes' in kwargs:
              includes = mesonlib.extract_as_list(kwargs, 'includes', pop = True)
              for inc in includes:
                  if hasattr(inc, 'held_object'):
                      inc = inc.held_object
                  if isinstance(inc, str):
                      scan_command += ['--include=%s' % (inc, )]
                  elif isinstance(inc,
  
  [... the rest is omitted ...]
files.new_path: mesonbuild/modules/gnome.py
files.old_contents: # Copyright 2015-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  '''This module provides helper functions for Gnome/GLib related
  functionality such as gobject-introspection, gresources and gtk-doc'''
  
  import os
  import copy
  import subprocess
  
  from .. import build
  from .. import mlog
  from .. import mesonlib
  from .. import compilers
  from .. import interpreter
  from . import GResourceTarget, GResourceHeaderTarget, GirTarget, TypelibTarget, VapiTarget
  from . import get_include_args
  from . import ExtensionModule
  from . import ModuleReturnValue
  from ..mesonlib import MesonException, OrderedSet, Popen_safe, extract_as_list
  from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
  from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs
  
  # gresource compilation is broken due to the way
  # the resource compiler and Ninja clash about it
  #
  # https://github.com/ninja-build/ninja/issues/1184
  # https://bugzilla.gnome.org/show_bug.cgi?id=774368
  gresource_dep_needed_version = '>= 2.51.1'
  
  native_glib_version = None
  girwarning_printed = False
  gdbuswarning_printed = False
  gresource_warning_printed = False
  _gir_has_extra_lib_arg = None
  
  def gir_has_extra_lib_arg(intr_obj):
      global _gir_has_extra_lib_arg
      if _gir_has_extra_lib_arg is not None:
          return _gir_has_extra_lib_arg
  
      _gir_has_extra_lib_arg = False
      try:
          g_ir_scanner = intr_obj.find_program_impl('g-ir-scanner').get_command()
          opts = Popen_safe(g_ir_scanner + ['--help'], stderr=subprocess.STDOUT)[1]
          _gir_has_extra_lib_arg = '--extra-library' in opts
      except (MesonException, FileNotFoundError, subprocess.CalledProcessError):
          pass
      return _gir_has_extra_lib_arg
  
  class GnomeModule(ExtensionModule):
      gir_dep = None
  
      @staticmethod
      def _get_native_glib_version(state):
          global native_glib_version
          if native_glib_version is None:
              glib_dep = PkgConfigDependency('glib-2.0', state.environment,
                                             {'native': True, 'required': False})
              if glib_dep.found():
                  native_glib_version = glib_dep.get_version()
              else:
                  mlog.warning('Could not detect glib version, assuming 2.54. '
                               'You may get build errors if your glib is older.')
                  native_glib_version = '2.54'
          return native_glib_version
  
      def __print_gresources_warning(self, state):
          global gresource_warning_printed
          if not gresource_warning_printed:
              if not mesonlib.version_compare(self._get_native_glib_version(state), gresource_dep_needed_version):
                  mlog.warning('GLib compiled dependencies do not work reliably with \n'
                               'the current version of GLib. See the following upstream issue:',
                               mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368'))
              gresource_warning_printed = True
          return []
  
      @staticmethod
      def _print_gdbus_warning():
          global gdbuswarning_printed
          if not gdbuswarning_printed:
              mlog.warning('Code generated with gdbus_codegen() requires the root directory be added to\n'
                           '  include_directories of targets with GLib < 2.51.3:',
                           mlog.bold('https://github.com/mesonbuild/meson/issues/1387'))
              gdbuswarning_printed = True
  
      @FeatureNewKwargs('gnome.compile_resources', '0.37.0', ['gresource_bundle', 'export', 'install_header'])
      @permittedKwargs({'source_dir', 'c_name', 'dependencies', 'export', 'gresource_bundle', 'install_header',
                        'install', 'install_dir', 'extra_args', 'build_by_default'})
      def compile_resources(self, state, args, kwargs):
          self.__print_gresources_warning(state)
          glib_version = self._get_native_glib_version(state)
  
          cmd = ['glib-compile-resources', '@INPUT@']
  
          source_dirs, dependencies = mesonlib.extract_as_list(kwargs, 'source_dir', 'dependencies', pop=True)
  
          if len(args) < 2:
              raise MesonException('Not enough arguments; the name of the resource '
                                   'and the path to the XML file are required')
  
          # Validate dependencies
          for (ii, dep) in enumerate(dependencies):
              if hasattr(dep, 'held_object'):
                  dependencies[ii] = dep = dep.held_object
              if not isinstance(dep, (mesonlib.File, build.CustomTarget, build.CustomTargetIndex)):
                  m = 'Unexpected dependency type {!r} for gnome.compile_resources() ' \
                      '"dependencies" argument.\nPlease pass the return value of ' \
                      'custom_target() or configure_file()'
                  raise MesonException(m.format(dep))
              if isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)):
                  if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
                      m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \
                          'be used with the current version of glib-compile-resources due to\n' \
                          '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>'
                      raise MesonException(m)
  
          ifile = args[1]
          if isinstance(ifile, mesonlib.File):
              # glib-compile-resources will be run inside the source dir,
              # so we need either 'src_to_build' or the absolute path.
              # Absolute path is the easiest choice.
              if ifile.is_built:
                  ifile = os.path.join(state.environment.get_build_dir(), ifile.subdir, ifile.fname)
              else:
                  ifile = os.path.join(ifile.subdir, ifile.fname)
          elif isinstance(ifile, str):
              ifile = os.path.join(state.subdir, ifile)
          elif isinstance(ifile, (interpreter.CustomTargetHolder,
                                  interpreter.CustomTargetIndexHolder,
                                  interpreter.GeneratedObjectsHolder)):
              m = 'Resource xml files generated at build-time cannot be used ' \
                  'with gnome.compile_resources() because we need to scan ' \
                  'the xml for dependencies. Use configure_file() instead ' \
                  'to generate it at configure-time.'
              raise MesonException(m)
          else:
              raise MesonException('Invalid file argument: {!r}'.format(ifile))
  
          depend_files, depends, subdirs = self._get_gresource_dependencies(
              state, ifile, source_dirs, dependencies)
  
          # Make source dirs relative to build dir now
          source_dirs = [os.path.join(state.build_to_src, state.subdir, d) for d in source_dirs]
          # Always include current directory, but after paths set by user
          source_dirs.append(os.path.join(state.build_to_src, state.subdir))
          # Ensure build directories of generated deps are included
          source_dirs += subdirs
  
          for source_dir in OrderedSet(source_dirs):
              cmd += ['--sourcedir', source_dir]
  
          if 'c_name' in kwargs:
              cmd += ['--c-name', kwargs.pop('c_name')]
          export = kwargs.pop('export', False)
          if not export:
              cmd += ['--internal']
  
          cmd += ['--generate', '--target', '@OUTPUT@']
  
          cmd += mesonlib.stringlistify(kwargs.pop('extra_args', []))
  
          gresource = kwargs.pop('gresource_bundle', False)
          if gresource:
              output = args[0] + '.gresource'
              name = args[0] + '_gresource'
          else:
              output = args[0] + '.c'
              name = args[0] + '_c'
  
          if kwargs.get('install', False) and not gresource:
              raise MesonException('The install kwarg only applies to gresource bundles, see install_header')
  
          install_header = kwargs.pop('install_header', False)
          if install_header and gresource:
              raise MesonException('The install_header kwarg does not apply to gresource bundles')
          if install_header and not export:
              raise MesonException('GResource header is installed yet export is not enabled')
  
          kwargs['input'] = args[1]
          kwargs['output'] = output
          kwargs['depends'] = depends
          if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
              # This will eventually go out of sync if dependencies are added
              kwargs['depend_files'] = depend_files
              kwargs['command'] = cmd
          else:
              depfile = kwargs['output'] + '.d'
              kwargs['depfile'] = depfile
              kwargs['command'] = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@']
          target_c = GResourceTarget(name, state.subdir, state.subproject, kwargs)
  
          if gresource: # Only one target for .gresource files
              return ModuleReturnValue(target_c, [target_c])
  
          h_kwargs = {
              'command': cmd,
              'input': args[1],
              'output': args[0] + '.h',
              # The header doesn't actually care about the files yet it errors if missing
              'depends': depends
          }
          if 'build_by_default' in kwargs:
              h_kwargs['build_by_default'] = kwargs['build_by_default']
          if install_header:
              h_kwargs['install'] = install_header
              h_kwargs['install_dir'] = kwargs.get('install_dir',
                                                   state.environment.coredata.get_builtin_option('includedir'))
          target_h = GResourceHeaderTarget(args[0] + '_h', state.subdir, state.subproject, h_kwargs)
          rv = [target_c, target_h]
          return ModuleReturnValue(rv, rv)
  
      def _get_gresource_dependencies(self, state, input_file, source_dirs, dependencies):
  
          cmd = ['glib-compile-resources',
                 input_file,
                 '--generate-dependencies']
  
          # Prefer generated files over source files
          cmd += ['--sourcedir', state.subdir] # Current build dir
          for source_dir in source_dirs:
              cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)]
  
          pc, stdout, stderr = Popen_safe(cmd, cwd=state.environment.get_source_dir())
          if pc.returncode != 0:
              m = 'glib-compile-resources failed to get dependencies for {}:\n{}'
              mlog.warning(m.format(cmd[1], stderr))
              raise subprocess.CalledProcessError(pc.returncode, cmd)
  
          dep_files = stdout.split('\n')[:-1]
  
          depends = []
          subdirs = []
          for resfile in dep_files[:]:
              resbasename = os.path.basename(resfile)
              for dep in dependencies:
                  if hasattr(dep, 'held_object'):
                      dep = dep.held_object
                  if isinstance(dep, mesonlib.File):
                      if dep.fname != resbasename:
                          continue
                      dep_files.remove(resfile)
                      dep_files.append(dep)
                      subdirs.append(dep.subdir)
                      break
                  elif isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)):
                      fname = None
                      outputs = {(o, os.path.basename(o)) for o in dep.get_outputs()}
                      for o, baseo in outputs:
                          if baseo == resbasename:
                              fname = o
                              break
                      if fname is not None:
                          dep_files.remove(resfile)
                          depends.append(dep)
                          subdirs.append(dep.get_subdir())
                          break
              else:
                  # In generate-dependencies mode, glib-compile-resources doesn't raise
                  # an error for missing resources but instead prints whatever filename
                  # was listed in the input file.  That's good because it means we can
                  # handle resource files that get generated as part of the build, as
                  # follows.
                  #
                  # If there are multiple generated resource files with the same basename
                  # then this code will get confused.
                  try:
                      f = mesonlib.File.from_source_file(state.environment.get_source_dir(),
                                                         ".", resfile)
                  except MesonException:
                      raise MesonException(
                          'Resource "%s" listed in "%s" was not found. If this is a '
                          'generated file, pass the target that generates it to '
                          'gnome.compile_resources() using the "dependencies" '
                          'keyword argument.' % (resfile, input_file))
                  dep_files.remove(resfile)
                  dep_files.append(f)
          return dep_files, depends, subdirs
  
      def _get_link_args(self, state, lib, depends, include_rpath=False,
                         use_gir_args=False):
          link_command = []
          # Construct link args
          if isinstance(lib, build.SharedLibrary):
              libdir = os.path.join(state.environment.get_build_dir(), state.backend.get_target_dir(lib))
              link_command.append('-L' + libdir)
              # Needed for the following binutils bug:
              # https://github.com/mesonbuild/meson/issues/1911
              # However, g-ir-scanner does not understand -Wl,-rpath
              # so we need to use -L instead
              for d in state.backend.determine_rpath_dirs(lib):
                  d = os.path.join(state.environment.get_build_dir(), d)
                  link_command.append('-L' + d)
                  if include_rpath:
                      link_command.append('-Wl,-rpath,' + d)
              if include_rpath:
                  link_command.append('-Wl,-rpath,' + libdir)
              depends.append(lib)
          if gir_has_extra_lib_arg(self.interpreter) and use_gir_args:
              link_command.append('--extra-library=' + lib.name)
          else:
              link_command.append('-l' + lib.name)
          return link_command
  
      def _get_dependencies_flags(self, deps, state, depends, include_rpath=False,
                                  use_gir_args=False):
          cflags = OrderedSet()
          internal_ldflags = OrderedSet()
          external_ldflags = OrderedSet()
          gi_includes = OrderedSet()
          deps = mesonlib.listify(deps, unholder=True)
  
          for dep in deps:
              if isinstance(dep, InternalDependency):
                  cflags.update(get_include_args(dep.include_directories))
                  for lib in dep.libraries:
                      if hasattr(lib, 'held_object'):
                          lib = lib.held_object
                      internal_ldflags.update(self._get_link_args(state, lib, depends, include_rpath))
                      libdepflags = self._get_dependencies_flags(lib.get_external_deps(), state, depends, include_rpath,
                                                                 use_gir_args)
                      cflags.update(libdepflags[0])
                      internal_ldflags.update(libdepflags[1])
                      external_ldflags.update(libdepflags[2])
                      gi_includes.update(libdepflags[3])
                  extdepflags = self._get_dependencies_flags(dep.ext_deps, state, depends, include_rpath,
                                                             use_gir_args)
                  cflags.update(extdepflags[0])
                  internal_ldflags.update(extdepflags[1])
                  external_ldflags.update(extdepflags[2])
                  gi_includes.update(extdepflags[3])
                  for source in dep.sources:
                      if hasattr(source, 'held_object'):
                          source = source.held_object
                      if isinstance(source, GirTarget):
                          gi_includes.update([os.path.join(state.environment.get_build_dir(),
                                              source.get_subdir())])
              # This should be any dependency other than an internal one.
              elif isinstance(dep, Dependency):
                  cflags.update(dep.get_compile_args())
                  for lib in dep.get_link_args(raw=True):
                      if (os.path.isabs(lib) and
                              # For PkgConfigDependency only:
                              getattr(dep, 'is_libtool', False)):
                          lib_dir = os.path.dirname(lib)
                          external_ldflags.update(["-L%s" % lib_dir])
                          if include_rpath:
                              external_ldflags.update(['-Wl,-rpath {}'.format(lib_dir)])
                          libname = os.path.basename(lib)
                          if libname.startswith("lib"):
                              libname = libname[3:]
                          libname = libname.split(".so")[0]
                          lib = "-l%s" % libname
                      # Hack to avoid passing some compiler options in
                      if lib.startswith("-W"):
                          continue
                      external_ldflags.update([lib])
  
                  if isinstance(dep, PkgConfigDependency):
                      girdir = dep.get_pkgconfig_variable("girdir", {'default': ''})
                      if girdir:
                          gi_includes.update([girdir])
              elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
                  cflags.update(get_include_args(dep.get_include_dirs()))
                  depends.append(dep)
              else:
                  mlog.log('dependency {!r} not handled to build gir files'.format(dep))
                  continue
  
          if gir_has_extra_lib_arg(self.interpreter) and use_gir_args:
              def fix_ldflags(ldflags):
                  fixed_ldflags = OrderedSet()
                  for ldflag in ldflags:
                      if ldflag.startswith("-l"):
                          fixed_ldflags.add(ldflag.replace('-l', '--extra-library=', 1))
                      else:
                          fixed_ldflags.add(ldflag)
                  return fixed_ldflags
              internal_ldflags = fix_ldflags(internal_ldflags)
              external_ldflags = fix_ldflags(external_ldflags)
          return cflags, internal_ldflags, external_ldflags, gi_includes
  
      @FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
      @permittedKwargs({'sources', 'nsversion', 'namespace', 'symbol_prefix', 'identifier_prefix',
                        'export_packages', 'includes', 'dependencies', 'link_with', 'include_directories',
                        'install', 'install_dir_gir', 'install_dir_typelib', 'extra_args',
                        'packages', 'header', 'build_by_default'})
      def generate_gir(self, state, args, kwargs):
          if len(args) != 1:
              raise MesonException('Gir takes one argument')
          if kwargs.get('install_dir'):
              raise MesonException('install_dir is not supported with generate_gir(), see "install_dir_gir" and "install_dir_typelib"')
          giscanner = self.interpreter.find_program_impl('g-ir-scanner')
          gicompiler = self.interpreter.find_program_impl('g-ir-compiler')
          girtarget = args[0]
          while hasattr(girtarget, 'held_object'):
              girtarget = girtarget.held_object
          if not isinstance(girtarget, (build.Executable, build.SharedLibrary)):
              raise MesonException('Gir target must be an executable or shared library')
          try:
              if not self.gir_dep:
                  self.gir_dep = PkgConfigDependency('gobject-introspection-1.0',
                                                     state.environment,
                                                     {'native': True})
              pkgargs = self.gir_dep.get_compile_args()
          except Exception:
              raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.')
          ns = kwargs.pop('namespace')
          nsversion = kwargs.pop('nsversion')
          libsources = mesonlib.extract_as_list(kwargs, 'sources', pop=True)
          girfile = '%s-%s.gir' % (ns, nsversion)
          srcdir = os.path.join(state.environment.get_source_dir(), state.subdir)
          builddir = os.path.join(state.environment.get_build_dir(), state.subdir)
          depends = [girtarget]
          gir_inc_dirs = []
  
          scan_command = [giscanner]
          scan_command += pkgargs
          scan_command += ['--no-libtool', '--namespace=' + ns, '--nsversion=' + nsversion, '--warn-all',
                           '--output', '@OUTPUT@']
  
          header = kwargs.pop('header', None)
          if header:
              if not isinstance(header, str):
                  raise MesonException('header must be a string')
              scan_command += ['--c-include=' + header]
  
          extra_args = mesonlib.stringlistify(kwargs.pop('extra_args', []))
          scan_command += extra_args
          scan_command += ['-I' + srcdir,
                           '-I' + builddir]
          scan_command += get_include_args(girtarget.get_include_dirs())
  
          gir_filelist_dir = state.backend.get_target_private_dir_abs(girtarget)
          if not os.path.isdir(gir_filelist_dir):
              os.mkdir(gir_filelist_dir)
          gir_filelist_filename = os.path.join(gir_filelist_dir, '%s_%s_gir_filelist' % (ns, nsversion))
  
          with open(gir_filelist_filename, 'w', encoding='utf-8') as gir_filelist:
              for s in libsources:
                  if hasattr(s, 'held_object'):
                      s = s.held_object
                  if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)):
                      for custom_output in s.get_outputs():
                          gir_filelist.write(os.path.join(state.environment.get_build_dir(),
                                                          state.backend.get_target_dir(s),
                                                          custom_output) + '\n')
                  elif isinstance(s, mesonlib.File):
                      gir_filelist.write(s.rel_to_builddir(state.build_to_src) + '\n')
                  elif isinstance(s, build.GeneratedList):
                      for gen_src in s.get_outputs():
                          gir_filelist.write(os.path.join(srcdir, gen_src) + '\n')
                  else:
                      gir_filelist.write(os.path.join(srcdir, s) + '\n')
          scan_command += ['--filelist=' + gir_filelist_filename]
  
          if 'link_with' in kwargs:
              link_with = mesonlib.extract_as_list(kwargs, 'link_with', pop = True)
  
              for link in link_with:
                  scan_command += self._get_link_args(state, link.held_object, depends,
                                                      use_gir_args=True)
  
          if 'includes' in kwargs:
              includes = mesonlib.extract_as_list(kwargs, 'includes', pop = True)
              for inc in includes:
                  if hasattr(inc, 'held_object'):
                      inc = inc.held_object
                  if isinstance(inc, str):
                      scan_command += ['--include=%s' % (inc, )]
                  elif isinstance(inc,
  
  [... the rest is omitted ...]
files.old_path: mesonbuild/modules/gnome.py