files: # Copyright 2012-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os, pickle, re
  from .. import build
  from .. import dependencies
  from .. import mesonlib
  from .. import mlog
  from .. import compilers
  import json
  import subprocess
  from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources
  from ..compilers import CompilerArgs
  
  class CleanTrees:
      '''
      Directories outputted by custom targets that have to be manually cleaned
      because on Linux `ninja clean` only deletes empty directories.
      '''
      def __init__(self, build_dir, trees):
          self.build_dir = build_dir
          self.trees = trees
  
  class InstallData:
      def __init__(self, source_dir, build_dir, prefix):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.prefix = prefix
          self.targets = []
          self.headers = []
          self.man = []
          self.data = []
          self.po_package_name = ''
          self.po = []
          self.install_scripts = []
          self.install_subdirs = []
  
  class ExecutableSerialisation:
      def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper,
                   workdir, extra_paths, capture):
          self.name = name
          self.fname = fname
          self.cmd_args = cmd_args
          self.env = env
          self.is_cross = is_cross
          self.exe_runner = exe_wrapper
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.capture = capture
  
  class TestSerialisation:
      def __init__(self, name, suite, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env,
                   should_fail, timeout, workdir, extra_paths):
          self.name = name
          self.suite = suite
          self.fname = fname
          self.is_cross = is_cross
          self.exe_runner = exe_wrapper
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.extra_paths = extra_paths
  
  # This class contains the basic functionality that is needed by all backends.
  # Feel free to move stuff in and out of it as you see fit.
  class Backend:
      def __init__(self, build):
          self.build = build
          self.environment = build.environment
          self.processed_targets = {}
          self.build_to_src = os.path.relpath(self.environment.get_source_dir(),
                                              self.environment.get_build_dir())
          for t in self.build.targets:
              priv_dirname = self.get_target_private_dir_abs(t)
              os.makedirs(priv_dirname, exist_ok=True)
  
      def get_target_filename(self, t):
          if isinstance(t, build.CustomTarget):
              if len(t.get_outputs()) != 1:
                  mlog.warning('custom_target {!r} has more than one output! '
                               'Using the first one.'.format(t.name))
              filename = t.get_outputs()[0]
          else:
              assert(isinstance(t, build.BuildTarget))
              filename = t.get_filename()
          return os.path.join(self.get_target_dir(t), filename)
  
      def get_target_filename_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))
  
      def get_target_filename_for_linking(self, target):
          # On some platforms (msvc for instance), the file that is used for
          # dynamic linking is not the same as the dynamic library itself. This
          # file is called an import library, and we want to link against that.
          # On all other platforms, we link to the library directly.
          if isinstance(target, build.SharedLibrary):
              link_lib = target.get_import_filename() or target.get_filename()
              return os.path.join(self.get_target_dir(target), link_lib)
          elif isinstance(target, build.StaticLibrary):
              return os.path.join(self.get_target_dir(target), target.get_filename())
          raise AssertionError('BUG: Tried to link to something that\'s not a library')
  
      def get_target_debug_filename(self, target):
          fname = target.get_debug_filename()
          if not fname:
              raise AssertionError("BUG: Tried to generate debug filename when it doesn't exist")
          return os.path.join(self.get_target_dir(target), fname)
  
      def get_target_dir(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              dirname = target.get_subdir()
          else:
              dirname = 'meson-out'
          return dirname
  
      def get_target_private_dir(self, target):
          dirname = os.path.join(self.get_target_dir(target), target.get_basename() + target.type_suffix())
          return dirname
  
      def get_target_private_dir_abs(self, target):
          dirname = os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target))
          return dirname
  
      def get_target_generated_dir(self, target, gensrc, src):
          """
          Takes a BuildTarget, a generator source (CustomTarget or GeneratedList),
          and a generated source filename.
          Returns the full path of the generated source relative to the build root
          """
          # CustomTarget generators output to the build dir of the CustomTarget
          if isinstance(gensrc, build.CustomTarget):
              return os.path.join(self.get_target_dir(gensrc), src)
          # GeneratedList generators output to the private build directory of the
          # target that the GeneratedList is used in
          return os.path.join(self.get_target_private_dir(target), src)
  
      def get_unity_source_filename(self, target, suffix):
          return target.name + '-unity.' + suffix
  
      def generate_unity_files(self, target, unity_src):
          abs_files = []
          result = []
          compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
  
          def init_language_file(suffix):
              outfilename = os.path.join(self.get_target_private_dir_abs(target),
                                         self.get_unity_source_filename(target, suffix))
              outfileabs = os.path.join(self.environment.get_build_dir(),
                                        outfilename)
              outfileabs_tmp = outfileabs + '.tmp'
              abs_files.append(outfileabs)
              outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp)
              if not os.path.exists(outfileabs_tmp_dir):
                  os.makedirs(outfileabs_tmp_dir)
              result.append(outfilename)
              return open(outfileabs_tmp, 'w')
  
          # For each language, generate a unity source file and return the list
          for comp, srcs in compsrcs.items():
              with init_language_file(comp.get_default_suffix()) as ofile:
                  for src in srcs:
                      ofile.write('#include<%s>\n' % src)
          [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files]
          return result
  
      def relpath(self, todir, fromdir):
          return os.path.relpath(os.path.join('dummyprefixdir', todir),
                                 os.path.join('dummyprefixdir', fromdir))
  
      def flatten_object_list(self, target, proj_dir_to_build_root=''):
          obj_list = []
          for obj in target.get_objects():
              if isinstance(obj, str):
                  o = os.path.join(proj_dir_to_build_root,
                                   self.build_to_src, target.get_subdir(), obj)
                  obj_list.append(o)
              elif isinstance(obj, mesonlib.File):
                  obj_list.append(obj.rel_to_builddir(self.build_to_src))
              elif isinstance(obj, build.ExtractedObjects):
                  obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root)
              else:
                  raise MesonException('Unknown data type in object list.')
          return obj_list
  
      def serialise_executable(self, exe, cmd_args, workdir, env={},
                               capture=None):
          import hashlib
          # Can't just use exe.name here; it will likely be run more than once
          if isinstance(exe, (dependencies.ExternalProgram,
                              build.BuildTarget, build.CustomTarget)):
              basename = exe.name
          else:
              basename = os.path.basename(exe)
          # Take a digest of the cmd args, env, workdir, and capture. This avoids
          # collisions and also makes the name deterministic over regenerations
          # which avoids a rebuild by Ninja because the cmdline stays the same.
          data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture),
                       encoding='utf-8')
          digest = hashlib.sha1(data).hexdigest()
          scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest)
          exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
          with open(exe_data, 'wb') as f:
              if isinstance(exe, dependencies.ExternalProgram):
                  exe_fullpath = exe.fullpath
                  exe_needs_wrapper = False
              elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
                  exe_fullpath = [self.get_target_filename_abs(exe)]
                  exe_needs_wrapper = exe.is_cross
              else:
                  exe_fullpath = [exe]
                  exe_needs_wrapper = False
              is_cross = exe_needs_wrapper and \
                  self.environment.is_cross_build() and \
                  self.environment.cross_info.need_cross_compiler() and \
                  self.environment.cross_info.need_exe_wrapper()
              if is_cross:
                  exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
              else:
                  exe_wrapper = None
              if mesonlib.is_windows():
                  extra_paths = self.determine_windows_extra_paths(exe)
              else:
                  extra_paths = []
              es = ExecutableSerialisation(basename, exe_fullpath, cmd_args, env,
                                           is_cross, exe_wrapper, workdir,
                                           extra_paths, capture)
              pickle.dump(es, f)
          return exe_data
  
      def serialise_tests(self):
          test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat')
          with open(test_data, 'wb') as datafile:
              self.write_test_file(datafile)
          benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat')
          with open(benchmark_data, 'wb') as datafile:
              self.write_benchmark_file(datafile)
          return test_data, benchmark_data
  
      def determine_linker(self, target):
          '''
          If we're building a static library, there is only one static linker.
          Otherwise, we query the target for the dynamic linker.
          '''
          if isinstance(target, build.StaticLibrary):
              if target.is_cross:
                  return self.build.static_cross_linker
              else:
                  return self.build.static_linker
          l = target.get_clike_dynamic_linker()
          if not l:
              m = "Couldn't determine linker for target {!r}"
              raise MesonException(m.format(target.name))
          return l
  
      def object_filename_from_source(self, target, source):
          if isinstance(source, mesonlib.File):
              source = source.fname
          # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o
          if source.endswith('.vala'):
              source = os.path.join(self.get_target_private_dir(target), source[:-5] + '.c')
          return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix()
  
      def determine_ext_objs(self, extobj, proj_dir_to_build_root):
          result = []
          targetdir = self.get_target_private_dir(extobj.target)
          # With unity builds, there's just one object that contains all the
          # sources, and we only support extracting all the objects in this mode,
          # so just return that.
          if self.environment.coredata.get_builtin_option('unity'):
              comp = get_compiler_for_source(extobj.target.compilers.values(),
                                             extobj.srclist[0])
              # The unity object name uses the full absolute path of the source file
              osrc = os.path.join(self.get_target_private_dir_abs(extobj.target),
                                  self.get_unity_source_filename(extobj.target,
                                                                 comp.get_default_suffix()))
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              return [objpath]
          for osrc in extobj.srclist:
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              result.append(objpath)
          return result
  
      def get_pch_include_args(self, compiler, target):
          args = []
          pchpath = self.get_target_private_dir(target)
          includeargs = compiler.get_include_args(pchpath, False)
          for lang in ['c', 'cpp']:
              p = target.get_pch(lang)
              if len(p) == 0:
                  continue
              if compiler.can_compile(p[-1]):
                  header = p[0]
                  args += compiler.get_pch_use_args(pchpath, header)
          if len(args) > 0:
              args = includeargs + args
          return args
  
      @staticmethod
      def escape_extra_args(compiler, args):
          # No extra escaping/quoting needed when not running on Windows
          if not mesonlib.is_windows():
              return args
          extra_args = []
          # Compiler-specific escaping is needed for -D args but not for any others
          if compiler.get_id() == 'msvc':
              # MSVC needs escaping when a -D argument ends in \ or \"
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      # Without extra escaping for these two, the next character
                      # gets eaten
                      if arg.endswith('\\'):
                          arg += '\\'
                      elif arg.endswith('\\"'):
                          arg = arg[:-2] + '\\\\"'
                  extra_args.append(arg)
          else:
              # MinGW GCC needs all backslashes in defines to be doubly-escaped
              # FIXME: Not sure about Cygwin or Clang
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      arg = arg.replace('\\', '\\\\')
                  extra_args.append(arg)
          return extra_args
  
      def generate_basic_compiler_args(self, target, compiler, no_warn_args=False):
          # Create an empty commands list, and start adding arguments from
          # various sources in the order in which they must override each other
          # starting from hard-coded defaults followed by build options and so on.
          commands = CompilerArgs(compiler)
          # First, the trivial ones that are impossible to override.
          #
          # Add -nostdinc/-nostdinc++ if needed; can't be overriden
          commands += self.get_cross_stdlib_args(target, compiler)
          # Add things like /NOLOGO or -pipe; usually can't be overriden
          commands += compiler.get_always_args()
          # Only add warning-flags by default if the buildtype enables it, and if
          # we weren't explicitly asked to not emit warnings (for Vala, f.ex)
          if no_warn_args:
              commands += compiler.get_no_warn_args()
          elif self.environment.coredata.get_builtin_option('buildtype') != 'plain':
              commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level'))
          # Add -Werror if werror=true is set in the build options set on the
          # command-line or default_options inside project(). This only sets the
          # action to be done for warnings if/when they are emitted, so it's ok
          # to set it after get_no_warn_args() or get_warn_args().
          if self.environment.coredata.get_builtin_option('werror'):
              commands += compiler.get_werror_args()
          # Add compile args for c_* or cpp_* build options set on the
          # command-line or default_options inside project().
          commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options)
          # Add buildtype args: optimization level, debugging, etc.
          commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype'))
          # Add compile args added using add_project_arguments()
          commands += self.build.get_project_args(compiler, target.subproject)
          # Add compile args added using add_global_arguments()
          # These override per-project arguments
          commands += self.build.get_global_args(compiler)
          # Compile args added from the env: CFLAGS/CXXFLAGS, etc. We want these
          # to override all the defaults, but not the per-target compile args.
          commands += self.environment.coredata.external_args[compiler.get_language()]
          # Always set -fPIC for shared libraries
          if isinstance(target, build.SharedLibrary):
              commands += compiler.get_pic_args()
          # Set -fPIC for static libraries by default unless explicitly disabled
          if isinstance(target, build.StaticLibrary) and target.pic:
              commands += compiler.get_pic_args()
          # Add compile args needed to find external dependencies
          # Link args are added while generating the link command
          for dep in target.get_external_deps():
              commands += dep.get_compile_args()
              # Qt needs -fPIC for executables
              # XXX: We should move to -fPIC for all executables
              if isinstance(target, build.Executable):
                  commands += dep.get_exe_args(compiler)
              # For 'automagic' deps: Boost and GTest. Also dependency('threads').
              # pkg-config puts the thread flags itself via `Cflags:`
              if dep.need_threads():
                  commands += compiler.thread_flags()
          # Fortran requires extra include directives.
          if compiler.language == 'fortran':
              for lt in target.link_targets:
                  priv_dir = os.path.join(self.get_target_dir(lt), lt.get_basename() + lt.type_suffix())
                  incflag = compiler.get_include_args(priv_dir, False)
                  commands += incflag
          return commands
  
      def build_target_link_arguments(self, compiler, deps):
          args = []
          for d in deps:
              if not isinstance(d, (build.StaticLibrary, build.SharedLibrary)):
                  raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename())
              if isinstance(compiler, compilers.LLVMDCompiler):
                  args += ['-L' + self.get_target_filename_for_linking(d)]
              else:
                  args.append(self.get_target_filename_for_linking(d))
              # If you have executable e that links to shared lib s1 that links to shared library s2
              # you have to specify s2 as well as s1 when linking e even if e does not directly use
              # s2. Gcc handles this case fine but Clang does not for some reason. Thus we need to
              # explictly specify all libraries every time.
              args += self.build_target_link_arguments(compiler, d.get_dependencies())
          return args
  
      def determine_windows_extra_paths(self, target):
          '''On Windows there is no such thing as an rpath.
          We must determine all locations of DLLs that this exe
          links to and return them so they can be used in unit
          tests.'''
          if not isinstance(target, build.Executable):
              return []
          prospectives = target.get_transitive_link_deps()
          result = []
          for ld in prospectives:
              if ld == '' or ld == '.':
                  continue
              dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld))
              if dirseg not in result:
                  result.append(dirseg)
          return result
  
      def write_benchmark_file(self, datafile):
          self.write_test_serialisation(self.build.get_benchmarks(), datafile)
  
      def write_test_file(self, datafile):
          self.write_test_serialisation(self.build.get_tests(), datafile)
  
      def write_test_serialisation(self, tests, datafile):
          arr = []
          for t in tests:
              exe = t.get_exe()
              if isinstance(exe, dependencies.ExternalProgram):
                  fname = exe.fullpath
              else:
                  fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
              is_cross = self.environment.is_cross_build() and \
                  self.environment.cross_info.need_cross_compiler() and \
                  self.environment.cross_info.need_exe_wrapper()
              if is_cross:
                  exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
              else:
                  exe_wrapper = None
              if mesonlib.is_windows():
                  extra_paths = self.determine_windows_extra_paths(exe)
              else:
                  extra_paths = []
              cmd_args = []
              for a in t.cmd_args:
                  if hasattr(a, 'held_object'):
                      a = a.held_object
                  if isinstance(a, mesonlib.File):
                      a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
                      cmd_args.append(a)
                  elif isinstance(a, str):
                      cmd_args.append(a)
                  elif isinstance(a, build.Target):
                      cmd_args.append(self.get_target_filename(a))
                  else:
                      raise MesonException('Bad object in test command.')
              ts = TestSerialisation(t.get_name(), t.suite, fname, is_cross, exe_wrapper,
                                     t.is_parallel, cmd_args, t.env, t.should_fail,
                                     t.timeout, t.workdir, extra_paths)
              arr.append(ts)
          pickle.dump(arr, datafile)
  
      def generate_depmf_install(self, d):
          if self.build.dep_manifest_name is None:
              return
          ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
          ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
          mfobj = {'type': 'dependency manifest',
                   'version': '1.0'}
          mfobj['projects'] = self.build.dep_manifest
          with open(ifilename, 'w') as f:
              f.write(json.dumps(mfobj))
          # Copy file from, to, and with mode unchanged
          d.data.append([ifilename, ofilename, None])
  
      def get_regen_filelist(self):
          '''List of all files whose alteration means that the build
          definition needs to be regenerated.'''
          deps = [os.path.join(self.build_to_src, df)
                  for df in self.interpreter.get_build_def_files()]
          if self.environment.is_cross_build():
              deps.append(os.path.join(self.build_to_src,
                                       self.environment.coredata.cross_file))
          deps.append('meson-private/coredata.dat')
          if os.path.exists(os.path.join(self.environment.get_source_dir(), 'meson_options.txt')):
              deps.append(os.path.join(self.build_to_src, 'meson_options.txt'))
          for sp in self.build.subprojects.keys():
              fname = os.path.join(self.environment.get_source_dir(), sp, 'meson_options.txt')
              if os.path.isfile(fname):
                  deps.append(os.path.join(self.build_to_src, sp, 'meson_options.txt'))
          return deps
  
      def exe_object_to_cmd_array(self, exe):
          if self.environment.is_cross_build() and \
             self.environment.cross_info.need_exe_wrapper() and \
             isinstance(exe, build.BuildTarget) and exe.is_cross:
              if 'exe_wrapper' not in self.environment.cross_info.config['binaries']:
                  s = 'Can not use target %s as a generator because it is cross-built\n'
                  s += 'and no exe wrapper is defined. You might want to set it to native instead.'
                  s = s % exe.name
                  raise MesonException(s)
          if isinstance(exe, build.BuildTarget):
              exe_arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))]
          else:
              exe_arr = exe.get_command()
          return exe_arr
  
      def replace_extra_args(self, args, genlist):
          final_args = []
          for a in args:
              if a == '@EXTRA_ARGS@':
                  final_args += genlist.get_extra_args()
              else:
                  final_args.append(a)
          return final_args
  
      def replace_outputs(self, args, private_dir, output_list):
          newargs = []
          regex = re.compile('@OUTPUT(\d+)@')
          for arg in args:
              m = regex.search(arg)
              while m is not None:
                  index = int(m.group(1))
                  src = '@OUTPUT%d@' % index
                  arg = arg.replace(src, os.path.join(private_dir, output_list[index]))
                  m = regex.search(arg)
              newargs.append(arg)
          return newargs
  
      def get_build_by_default_targets(self):
          result = {}
          # Get all build and custom targets that must be built by default
          for name, t in self.build.get_targets().items():
              if t.build_by_default or t.install or t.build_always:
                  result[name] = t
          # Get all targets used as test executables and arguments. These must
          # also be built by default. XXX: Sometime in the future these should be
          # built only before running tests.
          for t in self.build.get_tests():
              exe = t.exe
              if hasattr(exe, 'held_object'):
                  exe = exe.held_object
              if isinstance(exe, (build.CustomTarget, build.BuildTarget)):
                  result[exe.get_id()] = exe
              for arg in t.cmd_args:
                  if hasattr(arg, 'held_object'):
                      arg = arg.held_object
                  if not isinstance(arg, (build.CustomTarget, build.BuildTarget)):
                      continue
                  result[arg.get_id()] = arg
          return result
  
      def get_custom_target_provided_libraries(self, target):
          libs = []
          for t in target.get_generated_sources():
              if not isinstance(t, build.CustomTarget):
                  continue
              for f in t.output:
                  if self.environment.is_library(f):
                      libs.append(os.path.join(self.get_target_dir(t), f))
          return libs
  
      def get_custom_target_sources(self, target):
          '''
          Custom target sources can be of various object types; strings, File,
          BuildTarget, even other CustomTargets.
          Returns the path to them relative to the build root directory.
          '''
          srcs = []
          for i in target.get_sources():
              if hasattr(i, 'held_object'):
                  i = i.held_object
              if isinstance(i, str):
                  fname = [os.path.join(self.build_to_src, target.subdir, i)]
              elif isinstance(i, build.BuildTarget):
                  fname = [self.get_target_filename(i)]
              elif isinstance(i, build.CustomTarget):
                  fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
              elif isinstance(i, build.GeneratedList):
                  fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()]
              else:
                  fname = [i.rel_to_builddir(self.build_to_src)]
              if target.absolute_paths:
                  fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
              srcs += fname
          return srcs
  
      def eval_custom_target_command(self, target, absolute_outputs=False):
          # We want the outputs to be absolute only when using the VS backend
          outdir = self.get_target_dir(target)
          if absolute_outputs:
              outdir = os.path.join(self.environment.get_build_dir(), outdir)
          outputs = []
          for i in target.output:
              outputs.append(os.path.join(outdir, i))
          inputs = self.get_custom_target_sources(target)
          # Evaluate the command list
          cmd = []
          for i in target.command:
              if isinstance(i, build.Executable):
                  cmd += self.exe_object_to_cmd_array(i)
                  continue
              elif isinstance(i, build.CustomTarget):
                  # GIR scanner will attempt to execute this binary but
                  # it assumes that it is in path, so always give it a full path.
                  tmp = i.get_outputs()[0]
                  i = os.path.join(self.get_target_dir(i), tmp)
              elif isinstance(i, mesonlib.File):
                  i = i.rel_to_builddir(self.build_to_src)
                  if target.absolute_paths:
                      i = os.path.join(self.environment.get_build_dir(), i)
              # FIXME: str types are blindly added ignoring 'target.absolute_paths'
              # because we can't know if they refer to a file or just a string
              elif not isinstance(i, str):
                  err_msg = 'Argument {0} is of unknown type {1}'
                  raise RuntimeError(err_msg.format(str(i), str(type(i))))
              elif '@DEPFILE@' in i:
                  if target.depfile is None:
                      msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
                            'keyword argument.'.format(target.name)
                      raise MesonException(msg)
                  dfilename = os.path.join(outdir, target.depfile)
                  i = i.replace('@DEPFILE@', dfilename)
              elif '@PRIVATE_OUTDIR_' in i:
                  match = re.search('@PRIVATE_OUTDIR_(ABS_)?([^/\s*]*)@', i)
                  if not match:
                      msg = 'Custom target {!r} has an invalid argument {!r}' \
                            ''.format(target.name, i)
                      raise MesonException(msg)
                  source = match.group(0)
                  if match.group(1) is None and not target.absolute_paths:
                      lead_dir = ''
                  else:
                      lead_dir = self.environment.get_build_dir()
                  i = i.replace(source, os.path.join(lead_dir, outdir))
              cmd.append(i)
          # Substitute the rest of the template strings
          values = mesonlib.get_filenames_templates_dict(inputs, outputs)
          cmd = mesonlib.substitute_values(cmd, values)
          # This should not be necessary but removing it breaks
          # building GStreamer on Windows. The underlying issue
          # is problems with quoting backslashes on Windows
          # which is the seventh circle of hell. The downside is
          # that this breaks custom targets whose command lines
          # have backslashes. If you try to fix this be sure to
          # check that it does not break GST.
          #
          # The bug causes file paths such as c:\foo to get escaped
          # into c:\\foo.
          #
          # Unfortunately we have not been able to come up with an
          # isolated test case for this so unless you manage to come up
          # with one, the only way is to test the building with Gst's
          # setup. Note this in your MR or ping us and we will get it
          # fixed.
          #
          # https://github.com/mesonbuild/meson/pull/737
          cmd = [i.replace('\\', '/') for i in cmd]
          return inputs, outputs, cmd
  
      def run_postconf_scripts(self):
          env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
                 'MESON_BUILD_ROOT': self.environment.get_build_dir(),
                 }
          child_env = os.environ.copy()
          child_env.update(env)
  
          for s in self.build.postconf_scripts:
              cmd = s['exe'] + s['args']
              subprocess.check_call(cmd, env=child_env)
  
files: mesonbuild/backend/backends.py
files: # Copyright 2012-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os, pickle, re
  from .. import build
  from .. import dependencies
  from .. import mesonlib
  from .. import mlog
  from .. import compilers
  import json
  import subprocess
  from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources
  from ..compilers import CompilerArgs
  
  class CleanTrees:
      '''
      Directories outputted by custom targets that have to be manually cleaned
      because on Linux `ninja clean` only deletes empty directories.
      '''
      def __init__(self, build_dir, trees):
          self.build_dir = build_dir
          self.trees = trees
  
  class InstallData:
      def __init__(self, source_dir, build_dir, prefix):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.prefix = prefix
          self.targets = []
          self.headers = []
          self.man = []
          self.data = []
          self.po_package_name = ''
          self.po = []
          self.install_scripts = []
          self.install_subdirs = []
  
  class ExecutableSerialisation:
      def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper,
                   workdir, extra_paths, capture):
          self.name = name
          self.fname = fname
          self.cmd_args = cmd_args
          self.env = env
          self.is_cross = is_cross
          self.exe_runner = exe_wrapper
          self.workdir = workdir
          self.extra_paths = extra_paths
          self.capture = capture
  
  class TestSerialisation:
      def __init__(self, name, suite, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env,
                   should_fail, timeout, workdir, extra_paths):
          self.name = name
          self.suite = suite
          self.fname = fname
          self.is_cross = is_cross
          self.exe_runner = exe_wrapper
          self.is_parallel = is_parallel
          self.cmd_args = cmd_args
          self.env = env
          self.should_fail = should_fail
          self.timeout = timeout
          self.workdir = workdir
          self.extra_paths = extra_paths
  
  # This class contains the basic functionality that is needed by all backends.
  # Feel free to move stuff in and out of it as you see fit.
  class Backend:
      def __init__(self, build):
          self.build = build
          self.environment = build.environment
          self.processed_targets = {}
          self.build_to_src = os.path.relpath(self.environment.get_source_dir(),
                                              self.environment.get_build_dir())
          for t in self.build.targets:
              priv_dirname = self.get_target_private_dir_abs(t)
              os.makedirs(priv_dirname, exist_ok=True)
  
      def get_target_filename(self, t):
          if isinstance(t, build.CustomTarget):
              if len(t.get_outputs()) != 1:
                  mlog.warning('custom_target {!r} has more than one output! '
                               'Using the first one.'.format(t.name))
              filename = t.get_outputs()[0]
          else:
              assert(isinstance(t, build.BuildTarget))
              filename = t.get_filename()
          return os.path.join(self.get_target_dir(t), filename)
  
      def get_target_filename_abs(self, target):
          return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))
  
      def get_target_filename_for_linking(self, target):
          # On some platforms (msvc for instance), the file that is used for
          # dynamic linking is not the same as the dynamic library itself. This
          # file is called an import library, and we want to link against that.
          # On all other platforms, we link to the library directly.
          if isinstance(target, build.SharedLibrary):
              link_lib = target.get_import_filename() or target.get_filename()
              return os.path.join(self.get_target_dir(target), link_lib)
          elif isinstance(target, build.StaticLibrary):
              return os.path.join(self.get_target_dir(target), target.get_filename())
          raise AssertionError('BUG: Tried to link to something that\'s not a library')
  
      def get_target_debug_filename(self, target):
          fname = target.get_debug_filename()
          if not fname:
              raise AssertionError("BUG: Tried to generate debug filename when it doesn't exist")
          return os.path.join(self.get_target_dir(target), fname)
  
      def get_target_dir(self, target):
          if self.environment.coredata.get_builtin_option('layout') == 'mirror':
              dirname = target.get_subdir()
          else:
              dirname = 'meson-out'
          return dirname
  
      def get_target_private_dir(self, target):
          dirname = os.path.join(self.get_target_dir(target), target.get_basename() + target.type_suffix())
          return dirname
  
      def get_target_private_dir_abs(self, target):
          dirname = os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target))
          return dirname
  
      def get_target_generated_dir(self, target, gensrc, src):
          """
          Takes a BuildTarget, a generator source (CustomTarget or GeneratedList),
          and a generated source filename.
          Returns the full path of the generated source relative to the build root
          """
          # CustomTarget generators output to the build dir of the CustomTarget
          if isinstance(gensrc, build.CustomTarget):
              return os.path.join(self.get_target_dir(gensrc), src)
          # GeneratedList generators output to the private build directory of the
          # target that the GeneratedList is used in
          return os.path.join(self.get_target_private_dir(target), src)
  
      def get_unity_source_filename(self, target, suffix):
          return target.name + '-unity.' + suffix
  
      def generate_unity_files(self, target, unity_src):
          abs_files = []
          result = []
          compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
  
          def init_language_file(suffix):
              outfilename = os.path.join(self.get_target_private_dir_abs(target),
                                         self.get_unity_source_filename(target, suffix))
              outfileabs = os.path.join(self.environment.get_build_dir(),
                                        outfilename)
              outfileabs_tmp = outfileabs + '.tmp'
              abs_files.append(outfileabs)
              outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp)
              if not os.path.exists(outfileabs_tmp_dir):
                  os.makedirs(outfileabs_tmp_dir)
              result.append(outfilename)
              return open(outfileabs_tmp, 'w')
  
          # For each language, generate a unity source file and return the list
          for comp, srcs in compsrcs.items():
              with init_language_file(comp.get_default_suffix()) as ofile:
                  for src in srcs:
                      ofile.write('#include<%s>\n' % src)
          [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files]
          return result
  
      def relpath(self, todir, fromdir):
          return os.path.relpath(os.path.join('dummyprefixdir', todir),
                                 os.path.join('dummyprefixdir', fromdir))
  
      def flatten_object_list(self, target, proj_dir_to_build_root=''):
          obj_list = []
          for obj in target.get_objects():
              if isinstance(obj, str):
                  o = os.path.join(proj_dir_to_build_root,
                                   self.build_to_src, target.get_subdir(), obj)
                  obj_list.append(o)
              elif isinstance(obj, mesonlib.File):
                  obj_list.append(obj.rel_to_builddir(self.build_to_src))
              elif isinstance(obj, build.ExtractedObjects):
                  obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root)
              else:
                  raise MesonException('Unknown data type in object list.')
          return obj_list
  
      def serialise_executable(self, exe, cmd_args, workdir, env={},
                               capture=None):
          import hashlib
          # Can't just use exe.name here; it will likely be run more than once
          if isinstance(exe, (dependencies.ExternalProgram,
                              build.BuildTarget, build.CustomTarget)):
              basename = exe.name
          else:
              basename = os.path.basename(exe)
          # Take a digest of the cmd args, env, workdir, and capture. This avoids
          # collisions and also makes the name deterministic over regenerations
          # which avoids a rebuild by Ninja because the cmdline stays the same.
          data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture),
                       encoding='utf-8')
          digest = hashlib.sha1(data).hexdigest()
          scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest)
          exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
          with open(exe_data, 'wb') as f:
              if isinstance(exe, dependencies.ExternalProgram):
                  exe_fullpath = exe.fullpath
                  exe_needs_wrapper = False
              elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
                  exe_fullpath = [self.get_target_filename_abs(exe)]
                  exe_needs_wrapper = exe.is_cross
              else:
                  exe_fullpath = [exe]
                  exe_needs_wrapper = False
              is_cross = exe_needs_wrapper and \
                  self.environment.is_cross_build() and \
                  self.environment.cross_info.need_cross_compiler() and \
                  self.environment.cross_info.need_exe_wrapper()
              if is_cross:
                  exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
              else:
                  exe_wrapper = None
              if mesonlib.is_windows():
                  extra_paths = self.determine_windows_extra_paths(exe)
              else:
                  extra_paths = []
              es = ExecutableSerialisation(basename, exe_fullpath, cmd_args, env,
                                           is_cross, exe_wrapper, workdir,
                                           extra_paths, capture)
              pickle.dump(es, f)
          return exe_data
  
      def serialise_tests(self):
          test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat')
          with open(test_data, 'wb') as datafile:
              self.write_test_file(datafile)
          benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat')
          with open(benchmark_data, 'wb') as datafile:
              self.write_benchmark_file(datafile)
          return test_data, benchmark_data
  
      def determine_linker(self, target):
          '''
          If we're building a static library, there is only one static linker.
          Otherwise, we query the target for the dynamic linker.
          '''
          if isinstance(target, build.StaticLibrary):
              if target.is_cross:
                  return self.build.static_cross_linker
              else:
                  return self.build.static_linker
          l = target.get_clike_dynamic_linker()
          if not l:
              m = "Couldn't determine linker for target {!r}"
              raise MesonException(m.format(target.name))
          return l
  
      def object_filename_from_source(self, target, source):
          if isinstance(source, mesonlib.File):
              source = source.fname
          # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o
          if source.endswith('.vala'):
              source = os.path.join(self.get_target_private_dir(target), source[:-5] + '.c')
          return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix()
  
      def determine_ext_objs(self, extobj, proj_dir_to_build_root):
          result = []
          targetdir = self.get_target_private_dir(extobj.target)
          # With unity builds, there's just one object that contains all the
          # sources, and we only support extracting all the objects in this mode,
          # so just return that.
          if self.environment.coredata.get_builtin_option('unity'):
              comp = get_compiler_for_source(extobj.target.compilers.values(),
                                             extobj.srclist[0])
              # The unity object name uses the full absolute path of the source file
              osrc = os.path.join(self.get_target_private_dir_abs(extobj.target),
                                  self.get_unity_source_filename(extobj.target,
                                                                 comp.get_default_suffix()))
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              return [objpath]
          for osrc in extobj.srclist:
              objname = self.object_filename_from_source(extobj.target, osrc)
              objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
              result.append(objpath)
          return result
  
      def get_pch_include_args(self, compiler, target):
          args = []
          pchpath = self.get_target_private_dir(target)
          includeargs = compiler.get_include_args(pchpath, False)
          for lang in ['c', 'cpp']:
              p = target.get_pch(lang)
              if len(p) == 0:
                  continue
              if compiler.can_compile(p[-1]):
                  header = p[0]
                  args += compiler.get_pch_use_args(pchpath, header)
          if len(args) > 0:
              args = includeargs + args
          return args
  
      @staticmethod
      def escape_extra_args(compiler, args):
          # No extra escaping/quoting needed when not running on Windows
          if not mesonlib.is_windows():
              return args
          extra_args = []
          # Compiler-specific escaping is needed for -D args but not for any others
          if compiler.get_id() == 'msvc':
              # MSVC needs escaping when a -D argument ends in \ or \"
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      # Without extra escaping for these two, the next character
                      # gets eaten
                      if arg.endswith('\\'):
                          arg += '\\'
                      elif arg.endswith('\\"'):
                          arg = arg[:-2] + '\\\\"'
                  extra_args.append(arg)
          else:
              # MinGW GCC needs all backslashes in defines to be doubly-escaped
              # FIXME: Not sure about Cygwin or Clang
              for arg in args:
                  if arg.startswith('-D') or arg.startswith('/D'):
                      arg = arg.replace('\\', '\\\\')
                  extra_args.append(arg)
          return extra_args
  
      def generate_basic_compiler_args(self, target, compiler, no_warn_args=False):
          # Create an empty commands list, and start adding arguments from
          # various sources in the order in which they must override each other
          # starting from hard-coded defaults followed by build options and so on.
          commands = CompilerArgs(compiler)
          # First, the trivial ones that are impossible to override.
          #
          # Add -nostdinc/-nostdinc++ if needed; can't be overriden
          commands += self.get_cross_stdlib_args(target, compiler)
          # Add things like /NOLOGO or -pipe; usually can't be overriden
          commands += compiler.get_always_args()
          # Only add warning-flags by default if the buildtype enables it, and if
          # we weren't explicitly asked to not emit warnings (for Vala, f.ex)
          if no_warn_args:
              commands += compiler.get_no_warn_args()
          elif self.environment.coredata.get_builtin_option('buildtype') != 'plain':
              commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level'))
          # Add -Werror if werror=true is set in the build options set on the
          # command-line or default_options inside project(). This only sets the
          # action to be done for warnings if/when they are emitted, so it's ok
          # to set it after get_no_warn_args() or get_warn_args().
          if self.environment.coredata.get_builtin_option('werror'):
              commands += compiler.get_werror_args()
          # Add compile args for c_* or cpp_* build options set on the
          # command-line or default_options inside project().
          commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options)
          # Add buildtype args: optimization level, debugging, etc.
          commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype'))
          # Add compile args added using add_project_arguments()
          commands += self.build.get_project_args(compiler, target.subproject)
          # Add compile args added using add_global_arguments()
          # These override per-project arguments
          commands += self.build.get_global_args(compiler)
          # Compile args added from the env: CFLAGS/CXXFLAGS, etc. We want these
          # to override all the defaults, but not the per-target compile args.
          commands += self.environment.coredata.external_args[compiler.get_language()]
          # Always set -fPIC for shared libraries
          if isinstance(target, build.SharedLibrary):
              commands += compiler.get_pic_args()
          # Set -fPIC for static libraries by default unless explicitly disabled
          if isinstance(target, build.StaticLibrary) and target.pic:
              commands += compiler.get_pic_args()
          # Add compile args needed to find external dependencies
          # Link args are added while generating the link command
          for dep in target.get_external_deps():
              commands += dep.get_compile_args()
              # Qt needs -fPIC for executables
              # XXX: We should move to -fPIC for all executables
              if isinstance(target, build.Executable):
                  commands += dep.get_exe_args(compiler)
              # For 'automagic' deps: Boost and GTest. Also dependency('threads').
              # pkg-config puts the thread flags itself via `Cflags:`
              if dep.need_threads():
                  commands += compiler.thread_flags()
          # Fortran requires extra include directives.
          if compiler.language == 'fortran':
              for lt in target.link_targets:
                  priv_dir = os.path.join(self.get_target_dir(lt), lt.get_basename() + lt.type_suffix())
                  incflag = compiler.get_include_args(priv_dir, False)
                  commands += incflag
          return commands
  
      def build_target_link_arguments(self, compiler, deps):
          args = []
          for d in deps:
              if not isinstance(d, (build.StaticLibrary, build.SharedLibrary)):
                  raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename())
              if isinstance(compiler, compilers.LLVMDCompiler):
                  args += ['-L' + self.get_target_filename_for_linking(d)]
              else:
                  args.append(self.get_target_filename_for_linking(d))
              # If you have executable e that links to shared lib s1 that links to shared library s2
              # you have to specify s2 as well as s1 when linking e even if e does not directly use
              # s2. Gcc handles this case fine but Clang does not for some reason. Thus we need to
              # explictly specify all libraries every time.
              args += self.build_target_link_arguments(compiler, d.get_dependencies())
          return args
  
      def determine_windows_extra_paths(self, target):
          '''On Windows there is no such thing as an rpath.
          We must determine all locations of DLLs that this exe
          links to and return them so they can be used in unit
          tests.'''
          if not isinstance(target, build.Executable):
              return []
          prospectives = target.get_transitive_link_deps()
          result = []
          for ld in prospectives:
              if ld == '' or ld == '.':
                  continue
              dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld))
              if dirseg not in result:
                  result.append(dirseg)
          return result
  
      def write_benchmark_file(self, datafile):
          self.write_test_serialisation(self.build.get_benchmarks(), datafile)
  
      def write_test_file(self, datafile):
          self.write_test_serialisation(self.build.get_tests(), datafile)
  
      def write_test_serialisation(self, tests, datafile):
          arr = []
          for t in tests:
              exe = t.get_exe()
              if isinstance(exe, dependencies.ExternalProgram):
                  fname = exe.fullpath
              else:
                  fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
              is_cross = self.environment.is_cross_build() and \
                  self.environment.cross_info.need_cross_compiler() and \
                  self.environment.cross_info.need_exe_wrapper()
              if is_cross:
                  exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
              else:
                  exe_wrapper = None
              if mesonlib.is_windows():
                  extra_paths = self.determine_windows_extra_paths(exe)
              else:
                  extra_paths = []
              cmd_args = []
              for a in t.cmd_args:
                  if hasattr(a, 'held_object'):
                      a = a.held_object
                  if isinstance(a, mesonlib.File):
                      a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
                      cmd_args.append(a)
                  elif isinstance(a, str):
                      cmd_args.append(a)
                  elif isinstance(a, build.Target):
                      cmd_args.append(self.get_target_filename(a))
                  else:
                      raise MesonException('Bad object in test command.')
              ts = TestSerialisation(t.get_name(), t.suite, fname, is_cross, exe_wrapper,
                                     t.is_parallel, cmd_args, t.env, t.should_fail,
                                     t.timeout, t.workdir, extra_paths)
              arr.append(ts)
          pickle.dump(arr, datafile)
  
      def generate_depmf_install(self, d):
          if self.build.dep_manifest_name is None:
              return
          ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
          ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
          mfobj = {'type': 'dependency manifest',
                   'version': '1.0'}
          mfobj['projects'] = self.build.dep_manifest
          with open(ifilename, 'w') as f:
              f.write(json.dumps(mfobj))
          # Copy file from, to, and with mode unchanged
          d.data.append([ifilename, ofilename, None])
  
      def get_regen_filelist(self):
          '''List of all files whose alteration means that the build
          definition needs to be regenerated.'''
          deps = [os.path.join(self.build_to_src, df)
                  for df in self.interpreter.get_build_def_files()]
          if self.environment.is_cross_build():
              deps.append(os.path.join(self.build_to_src,
                                       self.environment.coredata.cross_file))
          deps.append('meson-private/coredata.dat')
          if os.path.exists(os.path.join(self.environment.get_source_dir(), 'meson_options.txt')):
              deps.append(os.path.join(self.build_to_src, 'meson_options.txt'))
          for sp in self.build.subprojects.keys():
              fname = os.path.join(self.environment.get_source_dir(), sp, 'meson_options.txt')
              if os.path.isfile(fname):
                  deps.append(os.path.join(self.build_to_src, sp, 'meson_options.txt'))
          return deps
  
      def exe_object_to_cmd_array(self, exe):
          if self.environment.is_cross_build() and \
             self.environment.cross_info.need_exe_wrapper() and \
             isinstance(exe, build.BuildTarget) and exe.is_cross:
              if 'exe_wrapper' not in self.environment.cross_info.config['binaries']:
                  s = 'Can not use target %s as a generator because it is cross-built\n'
                  s += 'and no exe wrapper is defined. You might want to set it to native instead.'
                  s = s % exe.name
                  raise MesonException(s)
          if isinstance(exe, build.BuildTarget):
              exe_arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))]
          else:
              exe_arr = exe.get_command()
          return exe_arr
  
      def replace_extra_args(self, args, genlist):
          final_args = []
          for a in args:
              if a == '@EXTRA_ARGS@':
                  final_args += genlist.get_extra_args()
              else:
                  final_args.append(a)
          return final_args
  
      def replace_outputs(self, args, private_dir, output_list):
          newargs = []
          regex = re.compile('@OUTPUT(\d+)@')
          for arg in args:
              m = regex.search(arg)
              while m is not None:
                  index = int(m.group(1))
                  src = '@OUTPUT%d@' % index
                  arg = arg.replace(src, os.path.join(private_dir, output_list[index]))
                  m = regex.search(arg)
              newargs.append(arg)
          return newargs
  
      def get_build_by_default_targets(self):
          result = {}
          # Get all build and custom targets that must be built by default
          for name, t in self.build.get_targets().items():
              if t.build_by_default or t.install or t.build_always:
                  result[name] = t
          # Get all targets used as test executables and arguments. These must
          # also be built by default. XXX: Sometime in the future these should be
          # built only before running tests.
          for t in self.build.get_tests():
              exe = t.exe
              if hasattr(exe, 'held_object'):
                  exe = exe.held_object
              if isinstance(exe, (build.CustomTarget, build.BuildTarget)):
                  result[exe.get_id()] = exe
              for arg in t.cmd_args:
                  if hasattr(arg, 'held_object'):
                      arg = arg.held_object
                  if not isinstance(arg, (build.CustomTarget, build.BuildTarget)):
                      continue
                  result[arg.get_id()] = arg
          return result
  
      def get_custom_target_provided_libraries(self, target):
          libs = []
          for t in target.get_generated_sources():
              if not isinstance(t, build.CustomTarget):
                  continue
              for f in t.output:
                  if self.environment.is_library(f):
                      libs.append(os.path.join(self.get_target_dir(t), f))
          return libs
  
      def get_custom_target_sources(self, target):
          '''
          Custom target sources can be of various object types; strings, File,
          BuildTarget, even other CustomTargets.
          Returns the path to them relative to the build root directory.
          '''
          srcs = []
          for i in target.get_sources():
              if hasattr(i, 'held_object'):
                  i = i.held_object
              if isinstance(i, str):
                  fname = [os.path.join(self.build_to_src, target.subdir, i)]
              elif isinstance(i, build.BuildTarget):
                  fname = [self.get_target_filename(i)]
              elif isinstance(i, build.CustomTarget):
                  fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()]
              elif isinstance(i, build.GeneratedList):
                  fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()]
              else:
                  fname = [i.rel_to_builddir(self.build_to_src)]
              if target.absolute_paths:
                  fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
              srcs += fname
          return srcs
  
      def eval_custom_target_command(self, target, absolute_outputs=False):
          # We only want the outputs to be absolute when using the VS backend
          if not absolute_outputs:
              ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output]
          else:
              ofilenames = [os.path.join(self.environment.get_build_dir(), self.get_target_dir(target), i)
                            for i in target.output]
          srcs = self.get_custom_target_sources(target)
          outdir = self.get_target_dir(target)
          # Many external programs fail on empty arguments.
          if outdir == '':
              outdir = '.'
          if target.absolute_paths:
              outdir = os.path.join(self.environment.get_build_dir(), outdir)
          cmd = []
          for i in target.command:
              if isinstance(i, build.Executable):
                  cmd += self.exe_object_to_cmd_array(i)
                  continue
              elif isinstance(i, build.CustomTarget):
                  # GIR scanner will attempt to execute this binary but
                  # it assumes that it is in path, so always give it a full path.
                  tmp = i.get_outputs()[0]
                  i = os.path.join(self.get_target_dir(i), tmp)
              elif isinstance(i, mesonlib.File):
                  i = i.rel_to_builddir(self.build_to_src)
                  if target.absolute_paths:
                      i = os.path.join(self.environment.get_build_dir(), i)
              # FIXME: str types are blindly added ignoring 'target.absolute_paths'
              elif not isinstance(i, str):
                  err_msg = 'Argument {0} is of unknown type {1}'
                  raise RuntimeError(err_msg.format(str(i), str(type(i))))
              for (j, src) in enumerate(srcs):
                  i = i.replace('@INPUT%d@' % j, src)
              for (j, res) in enumerate(ofilenames):
                  i = i.replace('@OUTPUT%d@' % j, res)
              if '@INPUT@' in i:
                  msg = 'Custom target {} has @INPUT@ in the command, but'.format(target.name)
                  if len(srcs) == 0:
                      raise MesonException(msg + ' no input files')
                  if i == '@INPUT@':
                      cmd += srcs
                      continue
                  else:
                      if len(srcs) > 1:
                          raise MesonException(msg + ' more than one input file')
                      i = i.replace('@INPUT@', srcs[0])
              elif '@OUTPUT@' in i:
                  msg = 'Custom target {} has @OUTPUT@ in the command, but'.format(target.name)
                  if len(ofilenames) == 0:
                      raise MesonException(msg + ' no output files')
                  if i == '@OUTPUT@':
                      cmd += ofilenames
                      continue
                  else:
                      if len(ofilenames) > 1:
                          raise MesonException(msg + ' more than one output file')
                      i = i.replace('@OUTPUT@', ofilenames[0])
              elif '@OUTDIR@' in i:
                  i = i.replace('@OUTDIR@', outdir)
              elif '@DEPFILE@' in i:
                  if target.depfile is None:
                      msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
                            'keyword argument.'.format(target.name)
                      raise MesonException(msg)
                  dfilename = os.path.join(outdir, target.depfile)
                  i = i.replace('@DEPFILE@', dfilename)
              elif '@PRIVATE_OUTDIR_' in i:
                  match = re.search('@PRIVATE_OUTDIR_(ABS_)?([^/\s*]*)@', i)
                  if not match:
                      msg = 'Custom target {!r} has an invalid argument {!r}' \
                            ''.format(target.name, i)
                      raise MesonException(msg)
                  source = match.group(0)
                  if match.group(1) is None and not target.absolute_paths:
                      lead_dir = ''
                  else:
                      lead_dir = self.environment.get_build_dir()
                  i = i.replace(source,
                                os.path.join(lead_dir,
                                             outdir))
              cmd.append(i)
          # This should not be necessary but removing it breaks
          # building GStreamer on Windows. The underlying issue
          # is problems with quoting backslashes on Windows
          # which is the seventh circle of hell. The downside is
          # that this breaks custom targets whose command lines
          # have backslashes. If you try to fix this be sure to
          # check that it does not break GST.
          #
          # The bug causes file paths such as c:\foo to get escaped
          # into c:\\foo.
          #
          # Unfortunately we have not been able to come up with an
          # isolated test case for this so unless you manage to come up
          # with one, the only way is to test the building with Gst's
          # setup. Note this in your MR or ping us and we will get it
          # fixed.
          #
          # https://github.com/mesonbuild/meson/pull/737
          cmd = [i.replace('\\', '/') for i in cmd]
          return srcs, ofilenames, cmd
  
      def run_postconf_scripts(self):
          env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
                 'MESON_BUILD_ROOT': self.environment.get_build_dir(),
                 }
          child_env = os.environ.copy()
          child_env.update(env)
  
          for s in self.build.postconf_scripts:
              cmd = s['exe'] + s['args']
              subprocess.check_call(cmd, env=child_env)
  
files: mesonbuild/backend/backends.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import environment
  from . import dependencies
  from . import mlog
  import copy, os, re
  from .mesonlib import File, flatten, MesonException, stringlistify, classify_unity_sources
  from .environment import for_windows, for_darwin
  from .compilers import is_object, clike_langs, lang_suffixes
  
  known_basic_kwargs = {'install': True,
                        'c_pch': True,
                        'cpp_pch': True,
                        'c_args': True,
                        'cpp_args': True,
                        'cs_args': True,
                        'vala_args': True,
                        'fortran_args': True,
                        'd_args': True,
                        'java_args': True,
                        'link_args': True,
                        'link_depends': True,
                        'link_with': True,
                        'include_directories': True,
                        'dependencies': True,
                        'install_dir': True,
                        'main_class': True,
                        'gui_app': True,
                        'extra_files': True,
                        'install_rpath': True,
                        'resources': True,
                        'sources': True,
                        'objects': True,
                        'native': True,
                        'build_by_default': True,
                        }
  
  # These contain kwargs supported by both static and shared libraries. These are
  # combined here because a library() call might be shared_library() or
  # static_library() at runtime based on the configuration.
  # FIXME: Find a way to pass that info down here so we can have proper target
  # kwargs checking when specifically using shared_library() or static_library().
  known_lib_kwargs = known_basic_kwargs.copy()
  known_lib_kwargs.update({'version': True, # Only for shared libs
                           'soversion': True, # Only for shared libs
                           'name_prefix': True,
                           'name_suffix': True,
                           'vs_module_defs': True, # Only for shared libs
                           'vala_header': True,
                           'vala_vapi': True,
                           'vala_gir': True,
                           'pic': True, # Only for static libs
                           })
  
  
  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 = {}
          self.compilers = {}
          self.cross_compilers = {}
          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.install_scripts = []
          self.postconf_scripts = []
          self.install_dirs = []
          self.dep_manifest_name = None
          self.dep_manifest = {}
          self.cross_stdlibs = {}
          self.test_setups = {}
  
      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 len(self.cross_compilers) == 0:
              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_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, is_unity):
          self.target = target
          self.srclist = srclist
          if 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 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.
          # If the list of sources for which we want objects is the same as the
          # list of sources that go into each unified build, we're good.
          srclist_set = set(self.srclist)
          # Objects for all the sources are required, so we're compatible
          if srclist_set == set(self.target.sources):
              return
          # Check if the srclist is a subset (of the target's sources) that is
          # going to form a unified source file and a single object
          compsrcs = classify_unity_sources(self.target.compilers.values(),
                                            self.target.sources)
          for srcs in compsrcs.values():
              if srclist_set == set(srcs):
                  return
          msg = 'Single object files can not be extracted in Unity builds. ' \
                'You can only extract all the object files at once.'
          raise MesonException(msg)
  
      def get_want_all_objects(self):
          return self.want_all_objects
  
  class EnvironmentVariables:
      def __init__(self):
          self.envvars = []
  
      def get_value(self, name, 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(name, values, kwargs)[1]
  
      def append(self, env, name, values, kwargs):
          sep, value = self.get_value(name, 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(name, values, kwargs)
          if name in env:
              return value + sep + env[name]
  
          return value
  
      def get_env(self, full_env):
          env = {}
          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, build_by_default):
          self.name = name
          self.subdir = subdir
          self.build_by_default = build_by_default
          self.install = False
          self.build_always = False
  
      def get_basename(self):
          return self.name
  
      def get_subdir(self):
          return self.subdir
  
      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.')
  
  class BuildTarget(Target):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, True)
          self.subproject = subproject # Can not be calculated from subdir as subproject dirname can be changed per project.
          self.is_cross = is_cross
          self.is_unity = environment.coredata.get_builtin_option('unity')
          self.environment = environment
          self.sources = []
          self.compilers = {}
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The file with debugging symbols
          self.debug_filename = None
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          # 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)
          if len(self.sources) == 0 \
                  and len(self.generated) == 0 \
                  and len(self.objects) == 0:
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers()
          self.validate_sources()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      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.
          base = self.name + self.type_suffix()
          if self.subproject == '':
              return base
          return self.subproject + '@@' + base
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, known_basic_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):
          if not isinstance(sources, list):
              sources = [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)):
                  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_sources(compiler, sources):
          for s in sources:
              if compiler.can_compile(s):
                  return True
          return False
  
      @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(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 len(self.sources) + len(self.generated) + len(self.objects) == 0:
              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)
          # 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:
              # Add compilers based on the above sources
              for lang, compiler in compilers.items():
                  # We try to be conservative because sometimes people add files
                  # in the list of sources that we can't determine the type based
                  # just on the suffix.
                  if self.can_compile_sources(compiler, sources):
                      self.compilers[lang] = compiler
          else:
              # No source files, target consists of only object files of unknown
              # origin. Just add the first clike compiler that we have and hope
              # that it can link these objects
              for lang in clike_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
          # 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 len(self.sources) == 0:
              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
          if 'rust' in self.compilers:
              firstname = self.sources[0]
              if isinstance(firstname, File):
                  firstname = firstname.fname
              first = os.path.split(firstname)[1]
              (base, suffix) = os.path.splitext(first)
              if suffix != '.rs' or self.name != base:
                  raise InvalidArguments('In Rust targets, the first source file must be named projectname.rs.')
  
      def get_original_kwargs(self):
          return self.kwargs
  
      def unpack_holder(self, d):
          if not isinstance(d, list):
              d = [d]
          newd = []
          for i in d:
              if hasattr(i, 'held_object'):
                  newd.append(i.held_object)
              else:
                  newd.append(i)
          return newd
  
      def copy_kwargs(self, kwargs):
          self.kwargs = copy.copy(kwargs)
          # This sucks quite badly. Arguments
          # are holders but they can't be pickled
          # so unpack those known.
          if 'dependencies' in self.kwargs:
              self.kwargs['dependencies'] = self.unpack_holder(self.kwargs['dependencies'])
          if 'link_with' in self.kwargs:
              self.kwargs['link_with'] = self.unpack_holder(self.kwargs['link_with'])
  
      def extract_objects(self, srclist):
          obj_src = []
          for src in srclist:
              if not isinstance(src, str):
                  raise MesonException('Object extraction arguments must be strings.')
              src = File(False, self.subdir, src)
              if src not in self.sources:
                  raise MesonException('Tried to extract unknown source %s.' % src)
              obj_src.append(src)
          return ExtractedObjects(self, obj_src, self.is_unity)
  
      def extract_all_objects(self):
          return ExtractedObjects(self, self.sources, self.is_unity)
  
      def get_all_link_deps(self):
          return self.get_transitive_link_deps()
  
      def get_transitive_link_deps(self):
          result = []
          for i in self.link_targets:
              result += i.get_all_link_deps()
          return result
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs)
          self.copy_kwargs(kwargs)
          kwargs.get('modules', [])
          self.need_install = kwargs.get('install', self.need_install)
          llist = kwargs.get('link_with', [])
          if not isinstance(llist, list):
              llist = [llist]
          for linktarget in llist:
              # Sorry for this hack. Keyword targets are kept in holders
              # in kwargs. Unpack here without looking at the exact type.
              if hasattr(linktarget, "held_object"):
                  linktarget = linktarget.held_object
              self.link(linktarget)
          c_pchlist = kwargs.get('c_pch', [])
          if not isinstance(c_pchlist, list):
              c_pchlist = [c_pchlist]
          self.add_pch('c', c_pchlist)
          cpp_pchlist = kwargs.get('cpp_pch', [])
          if not isinstance(cpp_pchlist, list):
              cpp_pchlist = [cpp_pchlist]
          self.add_pch('cpp', cpp_pchlist)
          clist = kwargs.get('c_args', [])
          if not isinstance(clist, list):
              clist = [clist]
          self.add_compiler_args('c', clist)
          cpplist = kwargs.get('cpp_args', [])
          if not isinstance(cpplist, list):
              cpplist = [cpplist]
          self.add_compiler_args('cpp', cpplist)
          cslist = kwargs.get('cs_args', [])
          if not isinstance(cslist, list):
              cslist = [cslist]
          self.add_compiler_args('cs', cslist)
          valalist = kwargs.get('vala_args', [])
          if not isinstance(valalist, list):
              valalist = [valalist]
          self.add_compiler_args('vala', valalist)
          fortranlist = kwargs.get('fortran_args', [])
          if not isinstance(fortranlist, list):
              fortranlist = [fortranlist]
          self.add_compiler_args('fortran', fortranlist)
          if not isinstance(self, Executable):
              self.vala_header = kwargs.get('vala_header', self.name + '.h')
              self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi')
          self.vala_gir = kwargs.get('vala_gir', None)
          dlist = stringlistify(kwargs.get('d_args', []))
          self.add_compiler_args('d', dlist)
          self.link_args = kwargs.get('link_args', [])
          if not isinstance(self.link_args, list):
              self.link_args = [self.link_args]
          for i in self.link_args:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_args arguments must be strings.')
          self.link_depends = kwargs.get('link_depends', [])
          if not isinstance(self.link_depends, list):
              self.link_depends = [self.link_depends]
          for i in self.link_depends:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_depends arguments must be strings.')
          deplist = kwargs.get('dependencies', [])
          if not isinstance(deplist, list):
              deplist = [deplist]
          self.add_deps(deplist)
          # Target-specific include dirs must be added after include dirs from
          # internal deps (added inside self.add_deps()) to override correctly.
          inclist = kwargs.get('include_directories', [])
          if not isinstance(inclist, list):
              inclist = [inclist]
          self.add_include_dirs(inclist)
          self.custom_install_dir = kwargs.get('install_dir', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InvalidArguments('Custom_install_dir must be a string')
          main_class = kwargs.get('main_class', '')
          if not isinstance(main_class, str):
              raise InvalidArguments('Main class must be a string')
          self.main_class = main_class
          if isinstance(self, Executable):
              self.gui_app = kwargs.get('gui_app', False)
              if not isinstance(self.gui_app, bool):
                  raise InvalidArguments('Argument gui_app must be boolean.')
          elif 'gui_app' in kwargs:
              raise InvalidArguments('Argument gui_app can only be used on executables.')
          extra_files = kwargs.get('extra_files', [])
          if isinstance(extra_files, str):
              extra_files = [extra_files]
          for i in extra_files:
              if not isinstance(i, str):
                  raise InvalidArguments('Arguments to extra_files must be strings.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, i)
              if not(os.path.isfile(trial)):
                  raise InvalidArguments('Tried to add non-existing extra file %s.' % i)
          self.extra_files = extra_files
          self.install_rpath = kwargs.get('install_rpath', '')
          if not isinstance(self.install_rpath, str):
              raise InvalidArguments('Install_rpath is not a string.')
          resources = kwargs.get('resources', [])
          if not isinstance(resources, list):
              resources = [resources]
          for r in resources:
              if not isinstance(r, str):
                  raise InvalidArguments('Resource argument is not a string.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, r)
              if not os.path.isfile(trial):
                  raise InvalidArguments('Tried to add non-existing resource %s.' % r)
          self.resources = resources
          if 'name_prefix' in kwargs:
              name_prefix = kwargs['name_prefix']
              if isinstance(name_prefix, list):
                  if len(name_prefix) != 0:
                      raise InvalidArguments('name_prefix array must be empty to signify null.')
              elif not isinstance(name_prefix, str):
                  raise InvalidArguments('name_prefix must be a string.')
              self.prefix = name_prefix
              self.name_prefix_set = True
          if 'name_suffix' in kwargs:
              name_suffix = kwargs['name_suffix']
              if isinstance(name_suffix, list):
                  if len(name_suffix) != 0:
                      raise InvalidArguments('name_suffix array must be empty to signify null.')
              else:
                  if not isinstance(name_suffix, str):
                      raise InvalidArguments('name_suffix must be a string.')
                  self.suffix = name_suffix
                  self.name_suffix_set = True
          if isinstance(self, StaticLibrary):
              # You can't disable PIC on OS X. The compiler ignores -fno-PIC.
              # PIC is always on for Windows (all code is position-independent
              # since library loading is done differently)
              if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment):
                  self.pic = True
              elif '-fPIC' in clist + cpplist:
                  mlog.warning("Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name))
                  self.pic = True
              else:
                  self.pic = kwargs.get('pic', False)
                  if not isinstance(self.pic, bool):
                      raise InvalidArguments('Argument pic to static library {!r} must be boolean'.format(self.name))
  
      def get_filename(self):
          return self.filename
  
      def get_outputs(self):
          return [self.filename]
  
      def get_debug_filename(self):
          """
          The name of the file that contains debugging symbols for this target
  
          Returns None if there are no debugging symbols or if they are embedded
          in the filename itself
          """
          return self.debug_filename
  
      def get_extra_args(self, language):
          return self.extra_args.get(language, [])
  
      def get_dependencies(self):
          transitive_deps = []
          for t in self.link_targets:
              transitive_deps.append(t)
              if isinstance(t, StaticLibrary):
                  transitive_deps += t.get_dependencies()
          return transitive_deps
  
      def get_source_subdir(self):
          return self.subdir
  
      def get_sources(self):
          return self.sources
  
      def get_objects(self):
          return self.objects
  
      def get_generated_sources(self):
          return self.generated
  
      def should_install(self):
          return self.need_install
  
      def has_pch(self):
          return len(self.pch) > 0
  
      def get_pch(self, language):
          try:
              return self.pch[language]
          except KeyError:
              return[]
  
      def get_include_dirs(self):
          return self.include_dirs
  
      def add_deps(self, deps):
          if not isinstance(deps, list):
              deps = [deps]
          for dep in deps:
              if hasattr(dep, 'held_object'):
                  dep = dep.held_object
              if isinstance(dep, dependencies.InternalDependency):
                  # Those parts that are internal.
                  self.process_sourcelist(dep.sources)
                  self.add_include_dirs(dep.include_directories)
                  for l in dep.libraries:
                      self.link(l)
                  # Those parts that are external.
                  extpart = dependencies.InternalDependency('undefined',
                                                            [],
                                                            dep.compile_args,
                                                            dep.link_args,
                                                            [], [], [])
                  self.external_deps.append(extpart)
                  # Deps of deps.
                  self.add_deps(dep.ext_deps)
              elif isinstance(dep, dependencies.Dependency):
                  self.external_deps.append(dep)
                  self.process_sourcelist(dep.get_sources())
              elif isinstance(dep, BuildTarget):
                  raise InvalidArguments('''Tried to use a build target as a dependency.
  You probably should put it in link_with instead.''')
              else:
                  # This is a bit of a hack. We do not want Build to know anything
                  # about the interpreter so we can't import it and use isinstance.
                  # This should be reliable enough.
                  if hasattr(dep, 'args_frozen'):
                      raise InvalidArguments('Tried to use subproject object as a dependency.\n'
                                             'You probably wanted to use a dependency declared in it instead.\n'
                                             'Access it by calling get_variable() on the subproject object.')
                  raise InvalidArguments('Argument is of an unacceptable type {!r}.\nMust be '
                                         'either an external dependency (returned by find_library() or '
                                         'dependency()) or an internal dependency (returned by '
                                         'declare_dependency()).'.format(type(dep).__name__))
  
      def get_external_deps(self):
          return self.external_deps
  
      def link(self, target):
          if not isinstance(target, list):
              target = [target]
          for t in target:
              if hasattr(t, 'held_object'):
                  t = t.held_object
              if not isinstance(t, (StaticLibrary, SharedLibrary)):
                  raise InvalidArguments('Link target {!r} is not library.'.format(t))
              if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic:
                  msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name)
                  msg += "Use the 'pic' option to static_library to build with PIC."
                  raise InvalidArguments(msg)
              if self.is_cross != t.is_cross:
                  raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name))
              self.link_targets.append(t)
  
      def add_pch(self, language, pchlist):
          if len(pchlist) == 0:
              return
          elif len(pchlist) == 1:
              if not environment.is_header(pchlist[0]):
                  raise InvalidArguments('PCH argument %s is not a header.' % pchlist[0])
          elif len(pchlist) == 2:
              if environment.is_header(pchlist[0]):
                  if not environment.is_source(pchlist[1]):
                      raise InvalidArguments('PCH definition must contain one header and at most one source.')
              elif environment.is_source(pchlist[0]):
                  if not environment.is_header(pchlist[1]):
                      raise InvalidArguments('PCH definition must contain one header and at most one source.')
                  pchlist = [pchlist[1], pchlist[0]]
              else:
                  raise InvalidArguments('PCH argument %s is of unknown type.' % pchlist[0])
          elif len(pchlist) > 2:
              raise InvalidArguments('PCH definition may have a maximum of 2 files.')
          self.pch[language] = pchlist
  
      def add_include_dirs(self, args):
          ids = []
          for a in args:
              # FIXME same hack, forcibly unpack from holder.
              if hasattr(a, 'held_object'):
                  a = a.held_object
              if not isinstance(a, IncludeDirs):
                  raise InvalidArguments('Include directory to be added is not an include directory object.')
              ids.append(a)
          self.include_dirs += ids
  
      def add_compiler_args(self, language, args):
          args = flatten(args)
          for a in args:
              if not isinstance(a, (str, File)):
                  raise InvalidArguments('A non-string passed to compiler args.')
          if language in self.extra_args:
              self.extra_args[language] += args
          else:
              self.extra_args[language] = args
  
      def get_aliases(self):
          return {}
  
      def get_clike_dynamic_linker(self):
          '''
          We use the order of languages in `clike_langs` to determine which
          linker to use in case the target has sources compiled with multiple
          compilers. All languages other than those in this list have their own
          linker.
          Note that Vala outputs C code, so Vala sources can use any linker
          that can link compiled C. We don't actually need to add an exception
          for Vala here because of that.
          '''
          for l in clike_langs:
              if l in self.compilers:
                  return self.compilers[l]
  
      def get_using_msvc(self):
          '''
          Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary,
          and SharedLibrary for deciding when to use MSVC-specific file naming
          and debug filenames.
  
          If at least some code is built with MSVC and the final library is
          linked with MSVC, we can be sure that some debug info will be
          generated. We only check the dynamic linker here because the static
          linker is guaranteed to be of the same type.
  
          Interesting cases:
          1. The Vala compiler outputs C code to be compiled by whatever
             C compiler we're using, so all objects will still be created by the
             MSVC compiler.
          2. If the target contains only objects, process_compilers guesses and
             picks the first compiler that smells right.
          '''
          linker = self.get_clike_dynamic_linker()
          if linker and linker.get_id() == 'msvc':
              return True
          return False
  
  
  class Generator:
      def __init__(self, args, kwargs):
          if len(args) != 1:
              raise InvalidArguments('Generator requires exactly one positional argument: the executable')
          exe = args[0]
          if hasattr(exe, 'held_object'):
              exe = exe.held_object
          if not isinstance(exe, (Executable, dependencies.ExternalProgram)):
              raise InvalidArguments('First generator argument must be an executable.')
          self.exe = exe
          self.depfile = None
          self.process_kwargs(kwargs)
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.exe)
  
      def get_exe(self):
          return self.exe
  
      def process_kwargs(self, kwargs):
          if 'arguments' not in kwargs:
              raise InvalidArguments('Generator must have "arguments" keyword argument.')
          args = kwargs['arguments']
          if isinstance(args, str):
              args = [args]
          if not isinstance(args, list):
              raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.')
          for a in args:
              if not isinstance(a, str):
                  raise InvalidArguments('A non-string object in "arguments" keyword argument.')
          self.arglist = args
          if 'output' not in kwargs:
              raise InvalidArguments('Generator must have "output" keyword argument.')
          outputs = kwargs['output']
          if not isinstance(outputs, list):
              outputs = [outputs]
          for rule in outputs:
              if not isinstance(rule, str):
                  raise InvalidArguments('"output" may only contain strings.')
              if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule:
                  raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.')
              if '/' in rule or '\\' in rule:
                  raise InvalidArguments('"outputs" must not contain a directory separator.')
          if len(outputs) > 1:
              for o in outputs:
                  if '@OUTPUT@' in o:
                      raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.')
          self.outputs = outputs
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InvalidArguments('Depfile must be a string.')
              if os.path.split(depfile)[1] != depfile:
                  raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
              self.depfile = depfile
  
      def get_base_outnames(self, inname):
          plainname = os.path.split(inname)[1]
          basename = os.path.splitext(plainname)[0]
          return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs]
  
      def get_dep_outname(self, inname):
          if self.depfile is None:
              raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.')
          plainname = os.path.split(inname)[1]
          basename = os.path.splitext(plainname)[0]
          return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
  
      def get_arglist(self):
          return self.arglist
  
      def process_files(self, name, files, state, extra_args=[]):
          output = GeneratedList(self, extra_args=extra_args)
          for f in files:
              if isinstance(f, str):
                  f = File.from_source_file(state.environment.source_dir, state.subdir, f)
              elif not isinstance(f, File):
                  raise InvalidArguments('{} arguments must be strings or files not {!r}.'.format(name, f))
              output.add_file(f)
          return output
  
  
  class GeneratedList:
      def __init__(self, generator, extra_args=[]):
          if hasattr(generator, 'held_object'):
              generator = generator.held_object
          self.generator = generator
          self.name = self.generator.exe
          self.infilelist = []
          self.outfilelist = []
          self.outmap = {}
          self.extra_depends = []
          self.extra_args = extra_args
  
      def add_file(self, newfile):
          self.infilelist.append(newfile)
          outfiles = self.generator.get_base_outnames(newfile.fname)
          self.outfilelist += outfiles
          self.outmap[newfile] = outfiles
  
      def get_inputs(self):
          return self.infilelist
  
      def get_outputs(self):
          return self.outfilelist
  
      def get_outputs_for(self, filename):
          return self.outmap[filename]
  
      def get_generator(self):
          return self.generator
  
      def get_extra_args(self):
          return self.extra_args
  
  class Executable(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          # Unless overriden, executables have no suffix or prefix. Except on
          # Windows and with C#/Mono executables where the suffix is 'exe'
          if not hasattr(self, 'prefix'):
              self.prefix = ''
          if not hasattr(self, 'suffix'):
              # Executable for Windows or C#/Mono
              if for_windows(is_cross, environment) or 'cs' in self.compilers:
                  self.suffix = 'exe'
              else:
                  self.suffix = ''
          self.filename = self.name
          if self.suffix:
              self.filename += '.' + self.suffix
          # See determine_debug_filenames() in build.SharedLibrary
          buildtype = environment.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              self.debug_filename = self.prefix + self.name + '.pdb'
  
      def type_suffix(self):
          return "@exe"
  
  class StaticLibrary(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options:
              kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          if 'cs' in self.compilers:
              raise InvalidArguments('Static libraries not supported for C#.')
          # By default a static library is named libfoo.a even on Windows because
          # MSVC does not have a consistent convention for what static libraries
          # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses
          # it and GCC only looks for static libraries called foo.lib and
          # libfoo.a. However, we cannot use foo.lib because that's the same as
          # the import library. Using libfoo.a is ok because people using MSVC
          # always pass the library filename while linking anyway.
          if not hasattr(self, 'prefix'):
              self.prefix = 'lib'
          if not hasattr(self, 'suffix'):
              # Rust static library crates have .rlib suffix
              if 'rust' in self.compilers:
                  self.suffix = 'rlib'
              else:
                  self.suffix = 'a'
          self.filename = self.prefix + self.name + '.' + self.suffix
          # See determine_debug_filenames() in build.SharedLibrary
          buildtype = environment.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              self.debug_filename = self.prefix + self.name + '.pdb'
  
      def type_suffix(self):
          return "@sta"
  
      def check_unknown_kwargs(self, kwargs):
          self.check_unknown_kwargs_int(kwargs, known_lib_kwargs)
  
  class SharedLibrary(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          self.soversion = None
          self.ltversion = None
          self.vs_module_defs = None
          # The import library this target will generate
          self.import_filename = None
          # The import library that Visual Studio would generate (and accept)
          self.vs_import_filename = None
          # The import library that GCC would generate (and prefer)
          self.gcc_import_filename = None
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          if not hasattr(self, 'prefix'):
              self.prefix = None
          if not hasattr(self, 'suffix'):
              self.suffix = None
          self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          self.determine_filenames(is_cross, environment)
          self.determine_debug_filenames(is_cross, environment)
  
      def determine_filenames(self, is_cross, env):
          """
          See https://github.com/mesonbuild/meson/pull/417 for details.
  
          First we determine the filename template (self.filename_tpl), then we
          set the output filename (self.filename).
  
          The template is needed while creating aliases (self.get_aliases),
          which are needed while generating .so shared libraries for Linux.
  
          Besides this, there's also the import library name, which is only used
          on Windows since on that platform the linker uses a separate library
          called the "import library" during linking instead of the shared
          library (DLL). The toolchain will output an import library in one of
          two formats: GCC or Visual Studio.
  
          When we're building with Visual Studio, the import library that will be
          generated by the toolchain is self.vs_import_filename, and with
          MinGW/GCC, it's self.gcc_import_filename. self.import_filename will
          always contain the import library name this target will generate.
          """
          prefix = ''
          suffix = ''
          self.filename_tpl = self.basic_filename_tpl
          # If the user already provided the prefix and suffix to us, we don't
          # need to do any filename suffix/prefix detection.
          # NOTE: manual prefix/suffix override is currently only tested for C/C++
          if self.prefix is not None and self.suffix is not None:
              pass
          # C# and Mono
          elif 'cs' in self.compilers:
              prefix = ''
              suffix = 'dll'
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          # Rust
          elif 'rust' in self.compilers:
              # Currently, we always build --crate-type=rlib
              prefix = 'lib'
              suffix = 'rlib'
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          # C, C++, Swift, Vala
          # Only Windows uses a separate import library for linking
          # For all other targets/platforms import_filename stays None
          elif for_windows(is_cross, env):
              suffix = 'dll'
              self.vs_import_filename = '{0}.lib'.format(self.name)
              self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name)
              if self.get_using_msvc():
                  # Shared library is of the form foo.dll
                  prefix = ''
                  # Import library is called foo.lib
                  self.import_filename = self.vs_import_filename
              # Assume GCC-compatible naming
              else:
                  # Shared library is of the form libfoo.dll
                  prefix = 'lib'
                  # Import library is called libfoo.dll.a
                  self.import_filename = self.gcc_import_filename
              # Shared library has the soversion if it is defined
              if self.soversion:
                  self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
              else:
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          elif for_darwin(is_cross, env):
              prefix = 'lib'
              suffix = 'dylib'
              # libfoo.dylib
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
              # On OS X, the filename should never have the soversion
              # See: https://github.com/mesonbuild/meson/pull/680
          else:
              prefix = 'lib'
              suffix = 'so'
              if self.ltversion:
                  # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional)
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}'
              elif self.soversion:
                  # libfoo.so.X
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}'
              else:
                  # No versioning, libfoo.so
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          if self.prefix is None:
              self.prefix = prefix
          if self.suffix is None:
              self.suffix = suffix
          self.filename = self.filename_tpl.format(self)
  
      def determine_debug_filenames(self, is_cross, env):
          """
          Determine the debug filename(s) using the prefix/name/etc detected in
          determine_filenames() above.
          """
          buildtype = env.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              # Currently we only implement separate debug symbol files for MSVC
              # since the toolchain does it for us. Other toolchains embed the
              # debugging symbols in the file itself by default.
              if self.soversion:
                  self.debug_filename = '{0.prefix}{0.name}-{0.soversion}.pdb'.format(self)
              else:
                  self.debug_filename = '{0.prefix}{0.name}.pdb'.format(self)
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs, environment)
          # Shared library version
          if 'version' in kwargs:
              self.ltversion = kwargs['version']
              if not isinstance(self.ltversion, str):
                  raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__)
              if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion):
                  raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion))
          # Try to extract/deduce the soversion
          if 'soversion' in kwargs:
              self.soversion = kwargs['soversion']
              if isinstance(self.soversion, int):
                  self.soversion = str(self.soversion)
              if not isinstance(self.soversion, str):
                  raise InvalidArguments('Shared library soversion is not a string or integer.')
          elif self.ltversion:
              # library version is defined, get the soversion from that
              # We replicate what Autotools does here and take the first
              # number of the version by default.
              self.soversion = self.ltversion.split('.')[0]
          # Visual Studio module-definitions file
          if 'vs_module_defs' in kwargs:
              path = kwargs['vs_module_defs']
              if os.path.isabs(path):
                  self.vs_module_defs = File.from_absolute_file(path)
              else:
                  self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path)
              # link_depends can be an absolute path or relative to self.subdir
              self.link_depends.append(path)
  
      def check_unknown_kwargs(self, kwargs):
          self.check_unknown_kwargs_int(kwargs, known_lib_kwargs)
  
      def get_import_filename(self):
          """
          The name of the import library that will be outputted by the compiler
  
          Returns None if there is no import library required for this platform
          """
          return self.import_filename
  
      def get_import_filenameslist(self):
          if self.import_filename:
              return [self.vs_import_filename, self.gcc_import_filename]
          return []
  
      def get_all_link_deps(self):
          return [self] + self.get_transitive_link_deps()
  
      def get_aliases(self):
          """
          If the versioned library name is libfoo.so.0.100.0, aliases are:
          * libfoo.so.0 (soversion) -> libfoo.so.0.100.0
          * libfoo.so (unversioned; for linking) -> libfoo.so.0
          """
          aliases = {}
          # Aliases are only useful with .so libraries. Also if the .so library
          # ends with .so (no versioning), we don't need aliases.
          if self.suffix != 'so' or self.filename.endswith('.so'):
              return {}
          # If ltversion != soversion we create an soversion alias:
          # libfoo.so.0 -> libfoo.so.0.100.0
          if self.ltversion and self.ltversion != self.soversion:
              if not self.soversion:
                  # This is done in self.process_kwargs()
                  raise AssertionError('BUG: If library version is defined, soversion must have been defined')
              alias_tpl = self.filename_tpl.replace('ltversion', 'soversion')
              ltversion_filename = alias_tpl.format(self)
              aliases[ltversion_filename] = self.filename
          else:
              ltversion_filename = self.filename
          # Unversioned alias: libfoo.so -> libfoo.so.0
          aliases[self.basic_filename_tpl.format(self)] = ltversion_filename
          return aliases
  
      def type_suffix(self):
          return "@sha"
  
  # A shared library that is meant to be used with dlopen rather than linking
  # into something else.
  class SharedModule(SharedLibrary):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          if 'version' in kwargs:
              raise MesonException('Shared modules must not specify the version kwarg.')
          if 'soversion' in kwargs:
              raise MesonException('Shared modules must not specify the soversion kwarg.')
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
  
  class CustomTarget(Target):
      known_kwargs = {'input': True,
                      'output': True,
                      'command': True,
                      'capture': False,
                      'install': True,
                      'install_dir': True,
                      'build_always': True,
                      'depends': True,
                      'depend_files': True,
                      'depfile': True,
                      'build_by_default': True,
                      }
  
      def __init__(self, name, subdir, kwargs, absolute_paths=False):
          super().__init__(name, subdir, False)
          self.dependencies = []
          self.extra_depends = []
          self.depend_files = [] # Files that this target depends on but are not on the command line.
          self.depfile = None
          self.process_kwargs(kwargs)
          self.extra_files = []
          # Whether to use absolute paths for all files on the commandline
          self.absolute_paths = absolute_paths
          unknowns = []
          for k in kwargs:
              if k not in CustomTarget.known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword arguments in target %s: %s' %
                           (self.name, ', '.join(unknowns)))
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
  
      def get_id(self):
          return self.name + self.type_suffix()
  
      def get_target_dependencies(self):
          deps = self.dependencies[:]
          deps += self.extra_depends
          for c in self.sources:
              if hasattr(c, 'held_object'):
                  c = c.held_object
              if isinstance(c, (BuildTarget, CustomTarget, GeneratedList)):
                  deps.append(c)
          return deps
  
      def flatten_command(self, cmd):
          if not isinstance(cmd, list):
              cmd = [cmd]
          final_cmd = []
          for c in cmd:
              if hasattr(c, 'held_object'):
                  c = c.held_object
              if isinstance(c, (str, File)):
                  final_cmd.append(c)
              elif isinstance(c, dependencies.ExternalProgram):
                  if not c.found():
                      m = 'Tried to use not-found external program {!r} in "command"'
                      raise InvalidArguments(m.format(c.name))
                  final_cmd += c.get_command()
              elif isinstance(c, (BuildTarget, CustomTarget)):
                  self.dependencies.append(c)
                  final_cmd.append(c)
              elif isinstance(c, list):
                  final_cmd += self.flatten_command(c)
              else:
                  raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
          return final_cmd
  
      def process_kwargs(self, kwargs):
          super().process_kwargs(kwargs)
          self.sources = kwargs.get('input', [])
          if not isinstance(self.sources, list):
              self.sources = [self.sources]
          if 'output' not in kwargs:
              raise InvalidArguments('Missing keyword argument "output".')
          self.output = kwargs['output']
          if not isinstance(self.output, list):
              self.output = [self.output]
          for i in self.output:
              if not(isinstance(i, str)):
                  raise InvalidArguments('Output argument not a string.')
              if '/' in i:
                  raise InvalidArguments('Output must not contain a path segment.')
          self.capture = kwargs.get('capture', False)
          if self.capture and len(self.output) != 1:
              raise InvalidArguments('Capturing can only output to a single file.')
          if 'command' not in kwargs:
              raise InvalidArguments('Missing keyword argument "command".')
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InvalidArguments('Depfile must be a string.')
              if os.path.split(depfile)[1] != depfile:
                  raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
              self.depfile = depfile
          self.command = self.flatten_command(kwargs['command'])
          if self.capture:
              for c in self.command:
                  if isinstance(c, str) and '@OUTPUT@' in c:
                      raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.')
          if 'install' in kwargs:
              self.install = kwargs['install']
              if not isinstance(self.install, bool):
                  raise InvalidArguments('"install" must be boolean.')
              if self.install:
                  if 'install_dir' not in kwargs:
                      raise InvalidArguments('"install_dir" not specified.')
                  self.install_dir = kwargs['install_dir']
                  if not(isinstance(self.install_dir, str)):
                      raise InvalidArguments('"install_dir" must be a string.')
          else:
              self.install = False
          self.build_always = kwargs.get('build_always', False)
          if not isinstance(self.build_always, bool):
              raise InvalidArguments('Argument build_always must be a boolean.')
          extra_deps = kwargs.get('depends', [])
          if not isinstance(extra_deps, list):
              extra_deps = [extra_deps]
          for ed in extra_deps:
              while hasattr(ed, 'held_object'):
                  ed = ed.held_object
              if not isinstance(ed, (CustomTarget, BuildTarget)):
                  raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library)')
              self.extra_depends.append(ed)
          depend_files = kwargs.get('depend_files', [])
          if not isinstance(depend_files, list):
              depend_files = [depend_files]
          for i in depend_files:
              if isinstance(i, (File, str)):
                  self.depend_files.append(i)
              else:
                  mlog.debug(i)
                  raise InvalidArguments('Unknown type {!r} in depend_files.'.format(type(i).__name__))
  
      def get_dependencies(self):
          return self.dependencies
  
      def should_install(self):
          return self.install
  
      def get_custom_install_dir(self):
          return self.install_dir
  
      def get_outputs(self):
          return self.output
  
      def get_filename(self):
          return self.output[0]
  
      def get_sources(self):
          return self.sources
  
      def get_generated_sources(self):
          return []
  
      def type_suffix(self):
          return "@cus"
  
  class RunTarget(Target):
      def __init__(self, name, command, args, dependencies, subdir):
          super().__init__(name, subdir, False)
          self.command = command
          self.args = args
          self.dependencies = dependencies
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
  
      def get_id(self):
          return self.name + self.type_suffix()
  
      def get_dependencies(self):
          return self.dependencies
  
      def get_generated_sources(self):
          return []
  
      def get_sources(self):
          return []
  
      def should_install(self):
          return False
  
      def get_filename(self):
          return self.name
  
      def type_suffix(self):
          return "@run"
  
  class Jar(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          for s in self.sources:
              if not s.endswith('.java'):
                  raise InvalidArguments('Jar source %s is not a java file.' % s)
          self.filename = self.name + '.jar'
          self.java_args = kwargs.get('java_args', [])
  
      def get_main_class(self):
          return self.main_class
  
      def type_suffix(self):
          return "@jar"
  
      def get_java_args(self):
          return self.java_args
  
  class ConfigureFile:
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          self.subdir = subdir
          self.sourcename = sourcename
          self.targetname = targetname
          self.configuration_data = configuration_data
  
      def __repr__(self):
          repr_str = "<{0}: {1} -> {2}>"
          src = os.path.join(self.subdir, self.sourcename)
          dst = os.path.join(self.subdir, self.targetname)
          return repr_str.format(self.__class__.__name__, src, dst)
  
      def get_configuration_data(self):
          return self.configuration_data
  
      def get_subdir(self):
          return self.subdir
  
      def get_source_name(self):
          return self.sourcename
  
      def get_target_name(self):
          return self.targetname
  
  class ConfigurationData:
      def __init__(self):
          super().__init__()
          self.values = {}
  
      def __repr__(self):
          return repr(self.values)
  
      def __contains__(self, value):
          return value in self.values
  
      def get(self, name):
          return self.values[name] # (val, desc)
  
      def keys(self):
          return self.values.keys()
  
  # A bit poorly named, but this represents plain data files to copy
  # during install.
  class Data:
      def __init__(self, sources, install_dir, install_mode=None):
          self.sources = sources
          self.install_dir = install_dir
          self.install_mode = install_mode
          if not isinstance(self.sources, list):
              self.sources = [self.sources]
          for s in self.sources:
              assert(isinstance(s, File))
  
  class RunScript(dict):
      def __init__(self, script, args):
          super().__init__()
          assert(isinstance(script, list))
          assert(isinstance(args, list))
          self['exe'] = script
          self['args'] = args
  
  class TestSetup:
      def __init__(self, *, exe_wrapper=None, gdb=None, timeout_multiplier=None, env=None):
          self.exe_wrapper = exe_wrapper
          self.gdb = gdb
          self.timeout_multiplier = timeout_multiplier
          self.env = env
  
  def get_sources_output_names(sources):
      '''
      For the specified list of @sources which can be strings, Files, or targets,
      get all the output basenames.
      '''
      names = []
      for s in sources:
          if hasattr(s, 'held_object'):
              s = s.held_object
          if isinstance(s, str):
              names.append(s)
          elif isinstance(s, (BuildTarget, CustomTarget, GeneratedList)):
              names += s.get_outputs()
          elif isinstance(s, File):
              names.append(s.fname)
          else:
              raise AssertionError('Unknown source type: {!r}'.format(s))
      return names
  
files: mesonbuild/build.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from . import environment
  from . import dependencies
  from . import mlog
  import copy, os, re
  from .mesonlib import File, flatten, MesonException, stringlistify, classify_unity_sources
  from .environment import for_windows, for_darwin
  from .compilers import is_object, clike_langs, lang_suffixes
  
  known_basic_kwargs = {'install': True,
                        'c_pch': True,
                        'cpp_pch': True,
                        'c_args': True,
                        'cpp_args': True,
                        'cs_args': True,
                        'vala_args': True,
                        'fortran_args': True,
                        'd_args': True,
                        'java_args': True,
                        'link_args': True,
                        'link_depends': True,
                        'link_with': True,
                        'include_directories': True,
                        'dependencies': True,
                        'install_dir': True,
                        'main_class': True,
                        'gui_app': True,
                        'extra_files': True,
                        'install_rpath': True,
                        'resources': True,
                        'sources': True,
                        'objects': True,
                        'native': True,
                        'build_by_default': True,
                        }
  
  # These contain kwargs supported by both static and shared libraries. These are
  # combined here because a library() call might be shared_library() or
  # static_library() at runtime based on the configuration.
  # FIXME: Find a way to pass that info down here so we can have proper target
  # kwargs checking when specifically using shared_library() or static_library().
  known_lib_kwargs = known_basic_kwargs.copy()
  known_lib_kwargs.update({'version': True, # Only for shared libs
                           'soversion': True, # Only for shared libs
                           'name_prefix': True,
                           'name_suffix': True,
                           'vs_module_defs': True, # Only for shared libs
                           'vala_header': True,
                           'vala_vapi': True,
                           'vala_gir': True,
                           'pic': True, # Only for static libs
                           })
  
  
  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 = {}
          self.compilers = {}
          self.cross_compilers = {}
          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.install_scripts = []
          self.postconf_scripts = []
          self.install_dirs = []
          self.dep_manifest_name = None
          self.dep_manifest = {}
          self.cross_stdlibs = {}
          self.test_setups = {}
  
      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 len(self.cross_compilers) == 0:
              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_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, is_unity):
          self.target = target
          self.srclist = srclist
          if 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 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.
          # If the list of sources for which we want objects is the same as the
          # list of sources that go into each unified build, we're good.
          srclist_set = set(self.srclist)
          # Objects for all the sources are required, so we're compatible
          if srclist_set == set(self.target.sources):
              return
          # Check if the srclist is a subset (of the target's sources) that is
          # going to form a unified source file and a single object
          compsrcs = classify_unity_sources(self.target.compilers.values(),
                                            self.target.sources)
          for srcs in compsrcs.values():
              if srclist_set == set(srcs):
                  return
          msg = 'Single object files can not be extracted in Unity builds. ' \
                'You can only extract all the object files at once.'
          raise MesonException(msg)
  
      def get_want_all_objects(self):
          return self.want_all_objects
  
  class EnvironmentVariables:
      def __init__(self):
          self.envvars = []
  
      def get_value(self, name, 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(name, values, kwargs)[1]
  
      def append(self, env, name, values, kwargs):
          sep, value = self.get_value(name, 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(name, values, kwargs)
          if name in env:
              return value + sep + env[name]
  
          return value
  
      def get_env(self, full_env):
          env = {}
          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, build_by_default):
          self.name = name
          self.subdir = subdir
          self.build_by_default = build_by_default
          self.install = False
          self.build_always = False
  
      def get_basename(self):
          return self.name
  
      def get_subdir(self):
          return self.subdir
  
      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.')
  
  class BuildTarget(Target):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, True)
          self.subproject = subproject # Can not be calculated from subdir as subproject dirname can be changed per project.
          self.is_cross = is_cross
          self.is_unity = environment.coredata.get_builtin_option('unity')
          self.environment = environment
          self.sources = []
          self.compilers = {}
          self.objects = []
          self.external_deps = []
          self.include_dirs = []
          self.link_targets = []
          self.link_depends = []
          self.name_prefix_set = False
          self.name_suffix_set = False
          self.filename = 'no_name'
          # The file with debugging symbols
          self.debug_filename = None
          self.need_install = False
          self.pch = {}
          self.extra_args = {}
          self.generated = []
          self.extra_files = []
          # 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)
          if len(self.sources) == 0 \
                  and len(self.generated) == 0 \
                  and len(self.objects) == 0:
              raise InvalidArguments('Build target %s has no sources.' % name)
          self.process_compilers()
          self.validate_sources()
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
  
      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.
          base = self.name + self.type_suffix()
          if self.subproject == '':
              return base
          return self.subproject + '@@' + base
  
      def check_unknown_kwargs(self, kwargs):
          # Override this method in derived classes that have more
          # keywords.
          self.check_unknown_kwargs_int(kwargs, known_basic_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):
          if not isinstance(sources, list):
              sources = [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)):
                  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_sources(compiler, sources):
          for s in sources:
              if compiler.can_compile(s):
                  return True
          return False
  
      @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(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 len(self.sources) + len(self.generated) + len(self.objects) == 0:
              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)
          # 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:
              # Add compilers based on the above sources
              for lang, compiler in compilers.items():
                  # We try to be conservative because sometimes people add files
                  # in the list of sources that we can't determine the type based
                  # just on the suffix.
                  if self.can_compile_sources(compiler, sources):
                      self.compilers[lang] = compiler
          else:
              # No source files, target consists of only object files of unknown
              # origin. Just add the first clike compiler that we have and hope
              # that it can link these objects
              for lang in clike_langs:
                  if lang in compilers:
                      self.compilers[lang] = compilers[lang]
                      break
          # 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 len(self.sources) == 0:
              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
          if 'rust' in self.compilers:
              firstname = self.sources[0]
              if isinstance(firstname, File):
                  firstname = firstname.fname
              first = os.path.split(firstname)[1]
              (base, suffix) = os.path.splitext(first)
              if suffix != '.rs' or self.name != base:
                  raise InvalidArguments('In Rust targets, the first source file must be named projectname.rs.')
  
      def get_original_kwargs(self):
          return self.kwargs
  
      def unpack_holder(self, d):
          if not isinstance(d, list):
              d = [d]
          newd = []
          for i in d:
              if hasattr(i, 'held_object'):
                  newd.append(i.held_object)
              else:
                  newd.append(i)
          return newd
  
      def copy_kwargs(self, kwargs):
          self.kwargs = copy.copy(kwargs)
          # This sucks quite badly. Arguments
          # are holders but they can't be pickled
          # so unpack those known.
          if 'dependencies' in self.kwargs:
              self.kwargs['dependencies'] = self.unpack_holder(self.kwargs['dependencies'])
          if 'link_with' in self.kwargs:
              self.kwargs['link_with'] = self.unpack_holder(self.kwargs['link_with'])
  
      def extract_objects(self, srclist):
          obj_src = []
          for src in srclist:
              if not isinstance(src, str):
                  raise MesonException('Object extraction arguments must be strings.')
              src = File(False, self.subdir, src)
              if src not in self.sources:
                  raise MesonException('Tried to extract unknown source %s.' % src)
              obj_src.append(src)
          return ExtractedObjects(self, obj_src, self.is_unity)
  
      def extract_all_objects(self):
          return ExtractedObjects(self, self.sources, self.is_unity)
  
      def get_all_link_deps(self):
          return self.get_transitive_link_deps()
  
      def get_transitive_link_deps(self):
          result = []
          for i in self.link_targets:
              result += i.get_all_link_deps()
          return result
  
      def get_custom_install_dir(self):
          return self.custom_install_dir
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs)
          self.copy_kwargs(kwargs)
          kwargs.get('modules', [])
          self.need_install = kwargs.get('install', self.need_install)
          llist = kwargs.get('link_with', [])
          if not isinstance(llist, list):
              llist = [llist]
          for linktarget in llist:
              # Sorry for this hack. Keyword targets are kept in holders
              # in kwargs. Unpack here without looking at the exact type.
              if hasattr(linktarget, "held_object"):
                  linktarget = linktarget.held_object
              self.link(linktarget)
          c_pchlist = kwargs.get('c_pch', [])
          if not isinstance(c_pchlist, list):
              c_pchlist = [c_pchlist]
          self.add_pch('c', c_pchlist)
          cpp_pchlist = kwargs.get('cpp_pch', [])
          if not isinstance(cpp_pchlist, list):
              cpp_pchlist = [cpp_pchlist]
          self.add_pch('cpp', cpp_pchlist)
          clist = kwargs.get('c_args', [])
          if not isinstance(clist, list):
              clist = [clist]
          self.add_compiler_args('c', clist)
          cpplist = kwargs.get('cpp_args', [])
          if not isinstance(cpplist, list):
              cpplist = [cpplist]
          self.add_compiler_args('cpp', cpplist)
          cslist = kwargs.get('cs_args', [])
          if not isinstance(cslist, list):
              cslist = [cslist]
          self.add_compiler_args('cs', cslist)
          valalist = kwargs.get('vala_args', [])
          if not isinstance(valalist, list):
              valalist = [valalist]
          self.add_compiler_args('vala', valalist)
          fortranlist = kwargs.get('fortran_args', [])
          if not isinstance(fortranlist, list):
              fortranlist = [fortranlist]
          self.add_compiler_args('fortran', fortranlist)
          if not isinstance(self, Executable):
              self.vala_header = kwargs.get('vala_header', self.name + '.h')
              self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi')
          self.vala_gir = kwargs.get('vala_gir', None)
          dlist = stringlistify(kwargs.get('d_args', []))
          self.add_compiler_args('d', dlist)
          self.link_args = kwargs.get('link_args', [])
          if not isinstance(self.link_args, list):
              self.link_args = [self.link_args]
          for i in self.link_args:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_args arguments must be strings.')
          self.link_depends = kwargs.get('link_depends', [])
          if not isinstance(self.link_depends, list):
              self.link_depends = [self.link_depends]
          for i in self.link_depends:
              if not isinstance(i, str):
                  raise InvalidArguments('Link_depends arguments must be strings.')
          deplist = kwargs.get('dependencies', [])
          if not isinstance(deplist, list):
              deplist = [deplist]
          self.add_deps(deplist)
          # Target-specific include dirs must be added after include dirs from
          # internal deps (added inside self.add_deps()) to override correctly.
          inclist = kwargs.get('include_directories', [])
          if not isinstance(inclist, list):
              inclist = [inclist]
          self.add_include_dirs(inclist)
          self.custom_install_dir = kwargs.get('install_dir', None)
          if self.custom_install_dir is not None:
              if not isinstance(self.custom_install_dir, str):
                  raise InvalidArguments('Custom_install_dir must be a string')
          main_class = kwargs.get('main_class', '')
          if not isinstance(main_class, str):
              raise InvalidArguments('Main class must be a string')
          self.main_class = main_class
          if isinstance(self, Executable):
              self.gui_app = kwargs.get('gui_app', False)
              if not isinstance(self.gui_app, bool):
                  raise InvalidArguments('Argument gui_app must be boolean.')
          elif 'gui_app' in kwargs:
              raise InvalidArguments('Argument gui_app can only be used on executables.')
          extra_files = kwargs.get('extra_files', [])
          if isinstance(extra_files, str):
              extra_files = [extra_files]
          for i in extra_files:
              if not isinstance(i, str):
                  raise InvalidArguments('Arguments to extra_files must be strings.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, i)
              if not(os.path.isfile(trial)):
                  raise InvalidArguments('Tried to add non-existing extra file %s.' % i)
          self.extra_files = extra_files
          self.install_rpath = kwargs.get('install_rpath', '')
          if not isinstance(self.install_rpath, str):
              raise InvalidArguments('Install_rpath is not a string.')
          resources = kwargs.get('resources', [])
          if not isinstance(resources, list):
              resources = [resources]
          for r in resources:
              if not isinstance(r, str):
                  raise InvalidArguments('Resource argument is not a string.')
              trial = os.path.join(environment.get_source_dir(), self.subdir, r)
              if not os.path.isfile(trial):
                  raise InvalidArguments('Tried to add non-existing resource %s.' % r)
          self.resources = resources
          if 'name_prefix' in kwargs:
              name_prefix = kwargs['name_prefix']
              if isinstance(name_prefix, list):
                  if len(name_prefix) != 0:
                      raise InvalidArguments('name_prefix array must be empty to signify null.')
              elif not isinstance(name_prefix, str):
                  raise InvalidArguments('name_prefix must be a string.')
              self.prefix = name_prefix
              self.name_prefix_set = True
          if 'name_suffix' in kwargs:
              name_suffix = kwargs['name_suffix']
              if isinstance(name_suffix, list):
                  if len(name_suffix) != 0:
                      raise InvalidArguments('name_suffix array must be empty to signify null.')
              else:
                  if not isinstance(name_suffix, str):
                      raise InvalidArguments('name_suffix must be a string.')
                  self.suffix = name_suffix
                  self.name_suffix_set = True
          if isinstance(self, StaticLibrary):
              # You can't disable PIC on OS X. The compiler ignores -fno-PIC.
              # PIC is always on for Windows (all code is position-independent
              # since library loading is done differently)
              if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment):
                  self.pic = True
              elif '-fPIC' in clist + cpplist:
                  mlog.warning("Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name))
                  self.pic = True
              else:
                  self.pic = kwargs.get('pic', False)
                  if not isinstance(self.pic, bool):
                      raise InvalidArguments('Argument pic to static library {!r} must be boolean'.format(self.name))
  
      def get_filename(self):
          return self.filename
  
      def get_outputs(self):
          return [self.filename]
  
      def get_debug_filename(self):
          """
          The name of the file that contains debugging symbols for this target
  
          Returns None if there are no debugging symbols or if they are embedded
          in the filename itself
          """
          return self.debug_filename
  
      def get_extra_args(self, language):
          return self.extra_args.get(language, [])
  
      def get_dependencies(self):
          transitive_deps = []
          for t in self.link_targets:
              transitive_deps.append(t)
              if isinstance(t, StaticLibrary):
                  transitive_deps += t.get_dependencies()
          return transitive_deps
  
      def get_source_subdir(self):
          return self.subdir
  
      def get_sources(self):
          return self.sources
  
      def get_objects(self):
          return self.objects
  
      def get_generated_sources(self):
          return self.generated
  
      def should_install(self):
          return self.need_install
  
      def has_pch(self):
          return len(self.pch) > 0
  
      def get_pch(self, language):
          try:
              return self.pch[language]
          except KeyError:
              return[]
  
      def get_include_dirs(self):
          return self.include_dirs
  
      def add_deps(self, deps):
          if not isinstance(deps, list):
              deps = [deps]
          for dep in deps:
              if hasattr(dep, 'held_object'):
                  dep = dep.held_object
              if isinstance(dep, dependencies.InternalDependency):
                  # Those parts that are internal.
                  self.process_sourcelist(dep.sources)
                  self.add_include_dirs(dep.include_directories)
                  for l in dep.libraries:
                      self.link(l)
                  # Those parts that are external.
                  extpart = dependencies.InternalDependency('undefined',
                                                            [],
                                                            dep.compile_args,
                                                            dep.link_args,
                                                            [], [], [])
                  self.external_deps.append(extpart)
                  # Deps of deps.
                  self.add_deps(dep.ext_deps)
              elif isinstance(dep, dependencies.Dependency):
                  self.external_deps.append(dep)
                  self.process_sourcelist(dep.get_sources())
              elif isinstance(dep, BuildTarget):
                  raise InvalidArguments('''Tried to use a build target as a dependency.
  You probably should put it in link_with instead.''')
              else:
                  # This is a bit of a hack. We do not want Build to know anything
                  # about the interpreter so we can't import it and use isinstance.
                  # This should be reliable enough.
                  if hasattr(dep, 'args_frozen'):
                      raise InvalidArguments('Tried to use subproject object as a dependency.\n'
                                             'You probably wanted to use a dependency declared in it instead.\n'
                                             'Access it by calling get_variable() on the subproject object.')
                  raise InvalidArguments('Argument is of an unacceptable type {!r}.\nMust be '
                                         'either an external dependency (returned by find_library() or '
                                         'dependency()) or an internal dependency (returned by '
                                         'declare_dependency()).'.format(type(dep).__name__))
  
      def get_external_deps(self):
          return self.external_deps
  
      def link(self, target):
          if not isinstance(target, list):
              target = [target]
          for t in target:
              if hasattr(t, 'held_object'):
                  t = t.held_object
              if not isinstance(t, (StaticLibrary, SharedLibrary)):
                  raise InvalidArguments('Link target {!r} is not library.'.format(t))
              if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic:
                  msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name)
                  msg += "Use the 'pic' option to static_library to build with PIC."
                  raise InvalidArguments(msg)
              if self.is_cross != t.is_cross:
                  raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name))
              self.link_targets.append(t)
  
      def add_pch(self, language, pchlist):
          if len(pchlist) == 0:
              return
          elif len(pchlist) == 1:
              if not environment.is_header(pchlist[0]):
                  raise InvalidArguments('PCH argument %s is not a header.' % pchlist[0])
          elif len(pchlist) == 2:
              if environment.is_header(pchlist[0]):
                  if not environment.is_source(pchlist[1]):
                      raise InvalidArguments('PCH definition must contain one header and at most one source.')
              elif environment.is_source(pchlist[0]):
                  if not environment.is_header(pchlist[1]):
                      raise InvalidArguments('PCH definition must contain one header and at most one source.')
                  pchlist = [pchlist[1], pchlist[0]]
              else:
                  raise InvalidArguments('PCH argument %s is of unknown type.' % pchlist[0])
          elif len(pchlist) > 2:
              raise InvalidArguments('PCH definition may have a maximum of 2 files.')
          self.pch[language] = pchlist
  
      def add_include_dirs(self, args):
          ids = []
          for a in args:
              # FIXME same hack, forcibly unpack from holder.
              if hasattr(a, 'held_object'):
                  a = a.held_object
              if not isinstance(a, IncludeDirs):
                  raise InvalidArguments('Include directory to be added is not an include directory object.')
              ids.append(a)
          self.include_dirs += ids
  
      def add_compiler_args(self, language, args):
          args = flatten(args)
          for a in args:
              if not isinstance(a, (str, File)):
                  raise InvalidArguments('A non-string passed to compiler args.')
          if language in self.extra_args:
              self.extra_args[language] += args
          else:
              self.extra_args[language] = args
  
      def get_aliases(self):
          return {}
  
      def get_clike_dynamic_linker(self):
          '''
          We use the order of languages in `clike_langs` to determine which
          linker to use in case the target has sources compiled with multiple
          compilers. All languages other than those in this list have their own
          linker.
          Note that Vala outputs C code, so Vala sources can use any linker
          that can link compiled C. We don't actually need to add an exception
          for Vala here because of that.
          '''
          for l in clike_langs:
              if l in self.compilers:
                  return self.compilers[l]
  
      def get_using_msvc(self):
          '''
          Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary,
          and SharedLibrary for deciding when to use MSVC-specific file naming
          and debug filenames.
  
          If at least some code is built with MSVC and the final library is
          linked with MSVC, we can be sure that some debug info will be
          generated. We only check the dynamic linker here because the static
          linker is guaranteed to be of the same type.
  
          Interesting cases:
          1. The Vala compiler outputs C code to be compiled by whatever
             C compiler we're using, so all objects will still be created by the
             MSVC compiler.
          2. If the target contains only objects, process_compilers guesses and
             picks the first compiler that smells right.
          '''
          linker = self.get_clike_dynamic_linker()
          if linker and linker.get_id() == 'msvc':
              return True
          return False
  
  
  class Generator:
      def __init__(self, args, kwargs):
          if len(args) != 1:
              raise InvalidArguments('Generator requires exactly one positional argument: the executable')
          exe = args[0]
          if hasattr(exe, 'held_object'):
              exe = exe.held_object
          if not isinstance(exe, (Executable, dependencies.ExternalProgram)):
              raise InvalidArguments('First generator argument must be an executable.')
          self.exe = exe
          self.depfile = None
          self.process_kwargs(kwargs)
  
      def __repr__(self):
          repr_str = "<{0}: {1}>"
          return repr_str.format(self.__class__.__name__, self.exe)
  
      def get_exe(self):
          return self.exe
  
      def process_kwargs(self, kwargs):
          if 'arguments' not in kwargs:
              raise InvalidArguments('Generator must have "arguments" keyword argument.')
          args = kwargs['arguments']
          if isinstance(args, str):
              args = [args]
          if not isinstance(args, list):
              raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.')
          for a in args:
              if not isinstance(a, str):
                  raise InvalidArguments('A non-string object in "arguments" keyword argument.')
          self.arglist = args
          if 'output' not in kwargs:
              raise InvalidArguments('Generator must have "output" keyword argument.')
          outputs = kwargs['output']
          if not isinstance(outputs, list):
              outputs = [outputs]
          for rule in outputs:
              if not isinstance(rule, str):
                  raise InvalidArguments('"output" may only contain strings.')
              if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule:
                  raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.')
              if '/' in rule or '\\' in rule:
                  raise InvalidArguments('"outputs" must not contain a directory separator.')
          if len(outputs) > 1:
              for o in outputs:
                  if '@OUTPUT@' in o:
                      raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.')
          self.outputs = outputs
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InvalidArguments('Depfile must be a string.')
              if os.path.split(depfile)[1] != depfile:
                  raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
              self.depfile = depfile
  
      def get_base_outnames(self, inname):
          plainname = os.path.split(inname)[1]
          basename = os.path.splitext(plainname)[0]
          return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs]
  
      def get_dep_outname(self, inname):
          if self.depfile is None:
              raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.')
          plainname = os.path.split(inname)[1]
          basename = os.path.splitext(plainname)[0]
          return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
  
      def get_arglist(self):
          return self.arglist
  
      def process_files(self, name, files, state, extra_args=[]):
          output = GeneratedList(self, extra_args=extra_args)
          for f in files:
              if isinstance(f, str):
                  f = File.from_source_file(state.environment.source_dir, state.subdir, f)
              elif not isinstance(f, File):
                  raise InvalidArguments('{} arguments must be strings or files not {!r}.'.format(name, f))
              output.add_file(f)
          return output
  
  
  class GeneratedList:
      def __init__(self, generator, extra_args=[]):
          if hasattr(generator, 'held_object'):
              generator = generator.held_object
          self.generator = generator
          self.name = self.generator.exe
          self.infilelist = []
          self.outfilelist = []
          self.outmap = {}
          self.extra_depends = []
          self.extra_args = extra_args
  
      def add_file(self, newfile):
          self.infilelist.append(newfile)
          outfiles = self.generator.get_base_outnames(newfile.fname)
          self.outfilelist += outfiles
          self.outmap[newfile] = outfiles
  
      def get_inputs(self):
          return self.infilelist
  
      def get_outputs(self):
          return self.outfilelist
  
      def get_outputs_for(self, filename):
          return self.outmap[filename]
  
      def get_generator(self):
          return self.generator
  
      def get_extra_args(self):
          return self.extra_args
  
  class Executable(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          # Unless overriden, executables have no suffix or prefix. Except on
          # Windows and with C#/Mono executables where the suffix is 'exe'
          if not hasattr(self, 'prefix'):
              self.prefix = ''
          if not hasattr(self, 'suffix'):
              # Executable for Windows or C#/Mono
              if for_windows(is_cross, environment) or 'cs' in self.compilers:
                  self.suffix = 'exe'
              else:
                  self.suffix = ''
          self.filename = self.name
          if self.suffix:
              self.filename += '.' + self.suffix
          # See determine_debug_filenames() in build.SharedLibrary
          buildtype = environment.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              self.debug_filename = self.prefix + self.name + '.pdb'
  
      def type_suffix(self):
          return "@exe"
  
  class StaticLibrary(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options:
              kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          if 'cs' in self.compilers:
              raise InvalidArguments('Static libraries not supported for C#.')
          # By default a static library is named libfoo.a even on Windows because
          # MSVC does not have a consistent convention for what static libraries
          # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses
          # it and GCC only looks for static libraries called foo.lib and
          # libfoo.a. However, we cannot use foo.lib because that's the same as
          # the import library. Using libfoo.a is ok because people using MSVC
          # always pass the library filename while linking anyway.
          if not hasattr(self, 'prefix'):
              self.prefix = 'lib'
          if not hasattr(self, 'suffix'):
              # Rust static library crates have .rlib suffix
              if 'rust' in self.compilers:
                  self.suffix = 'rlib'
              else:
                  self.suffix = 'a'
          self.filename = self.prefix + self.name + '.' + self.suffix
          # See determine_debug_filenames() in build.SharedLibrary
          buildtype = environment.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              self.debug_filename = self.prefix + self.name + '.pdb'
  
      def type_suffix(self):
          return "@sta"
  
      def check_unknown_kwargs(self, kwargs):
          self.check_unknown_kwargs_int(kwargs, known_lib_kwargs)
  
  class SharedLibrary(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          self.soversion = None
          self.ltversion = None
          self.vs_module_defs = None
          # The import library this target will generate
          self.import_filename = None
          # The import library that Visual Studio would generate (and accept)
          self.vs_import_filename = None
          # The import library that GCC would generate (and prefer)
          self.gcc_import_filename = None
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          if not hasattr(self, 'prefix'):
              self.prefix = None
          if not hasattr(self, 'suffix'):
              self.suffix = None
          self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          self.determine_filenames(is_cross, environment)
          self.determine_debug_filenames(is_cross, environment)
  
      def determine_filenames(self, is_cross, env):
          """
          See https://github.com/mesonbuild/meson/pull/417 for details.
  
          First we determine the filename template (self.filename_tpl), then we
          set the output filename (self.filename).
  
          The template is needed while creating aliases (self.get_aliases),
          which are needed while generating .so shared libraries for Linux.
  
          Besides this, there's also the import library name, which is only used
          on Windows since on that platform the linker uses a separate library
          called the "import library" during linking instead of the shared
          library (DLL). The toolchain will output an import library in one of
          two formats: GCC or Visual Studio.
  
          When we're building with Visual Studio, the import library that will be
          generated by the toolchain is self.vs_import_filename, and with
          MinGW/GCC, it's self.gcc_import_filename. self.import_filename will
          always contain the import library name this target will generate.
          """
          prefix = ''
          suffix = ''
          self.filename_tpl = self.basic_filename_tpl
          # If the user already provided the prefix and suffix to us, we don't
          # need to do any filename suffix/prefix detection.
          # NOTE: manual prefix/suffix override is currently only tested for C/C++
          if self.prefix is not None and self.suffix is not None:
              pass
          # C# and Mono
          elif 'cs' in self.compilers:
              prefix = ''
              suffix = 'dll'
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          # Rust
          elif 'rust' in self.compilers:
              # Currently, we always build --crate-type=rlib
              prefix = 'lib'
              suffix = 'rlib'
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          # C, C++, Swift, Vala
          # Only Windows uses a separate import library for linking
          # For all other targets/platforms import_filename stays None
          elif for_windows(is_cross, env):
              suffix = 'dll'
              self.vs_import_filename = '{0}.lib'.format(self.name)
              self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name)
              if self.get_using_msvc():
                  # Shared library is of the form foo.dll
                  prefix = ''
                  # Import library is called foo.lib
                  self.import_filename = self.vs_import_filename
              # Assume GCC-compatible naming
              else:
                  # Shared library is of the form libfoo.dll
                  prefix = 'lib'
                  # Import library is called libfoo.dll.a
                  self.import_filename = self.gcc_import_filename
              # Shared library has the soversion if it is defined
              if self.soversion:
                  self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
              else:
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          elif for_darwin(is_cross, env):
              prefix = 'lib'
              suffix = 'dylib'
              # libfoo.dylib
              self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
              # On OS X, the filename should never have the soversion
              # See: https://github.com/mesonbuild/meson/pull/680
          else:
              prefix = 'lib'
              suffix = 'so'
              if self.ltversion:
                  # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional)
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}'
              elif self.soversion:
                  # libfoo.so.X
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}'
              else:
                  # No versioning, libfoo.so
                  self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
          if self.prefix is None:
              self.prefix = prefix
          if self.suffix is None:
              self.suffix = suffix
          self.filename = self.filename_tpl.format(self)
  
      def determine_debug_filenames(self, is_cross, env):
          """
          Determine the debug filename(s) using the prefix/name/etc detected in
          determine_filenames() above.
          """
          buildtype = env.coredata.get_builtin_option('buildtype')
          if self.get_using_msvc() and buildtype.startswith('debug'):
              # Currently we only implement separate debug symbol files for MSVC
              # since the toolchain does it for us. Other toolchains embed the
              # debugging symbols in the file itself by default.
              if self.soversion:
                  self.debug_filename = '{0.prefix}{0.name}-{0.soversion}.pdb'.format(self)
              else:
                  self.debug_filename = '{0.prefix}{0.name}.pdb'.format(self)
  
      def process_kwargs(self, kwargs, environment):
          super().process_kwargs(kwargs, environment)
          # Shared library version
          if 'version' in kwargs:
              self.ltversion = kwargs['version']
              if not isinstance(self.ltversion, str):
                  raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__)
              if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion):
                  raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion))
          # Try to extract/deduce the soversion
          if 'soversion' in kwargs:
              self.soversion = kwargs['soversion']
              if isinstance(self.soversion, int):
                  self.soversion = str(self.soversion)
              if not isinstance(self.soversion, str):
                  raise InvalidArguments('Shared library soversion is not a string or integer.')
          elif self.ltversion:
              # library version is defined, get the soversion from that
              # We replicate what Autotools does here and take the first
              # number of the version by default.
              self.soversion = self.ltversion.split('.')[0]
          # Visual Studio module-definitions file
          if 'vs_module_defs' in kwargs:
              path = kwargs['vs_module_defs']
              if os.path.isabs(path):
                  self.vs_module_defs = File.from_absolute_file(path)
              else:
                  self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path)
              # link_depends can be an absolute path or relative to self.subdir
              self.link_depends.append(path)
  
      def check_unknown_kwargs(self, kwargs):
          self.check_unknown_kwargs_int(kwargs, known_lib_kwargs)
  
      def get_import_filename(self):
          """
          The name of the import library that will be outputted by the compiler
  
          Returns None if there is no import library required for this platform
          """
          return self.import_filename
  
      def get_import_filenameslist(self):
          if self.import_filename:
              return [self.vs_import_filename, self.gcc_import_filename]
          return []
  
      def get_all_link_deps(self):
          return [self] + self.get_transitive_link_deps()
  
      def get_aliases(self):
          """
          If the versioned library name is libfoo.so.0.100.0, aliases are:
          * libfoo.so.0 (soversion) -> libfoo.so.0.100.0
          * libfoo.so (unversioned; for linking) -> libfoo.so.0
          """
          aliases = {}
          # Aliases are only useful with .so libraries. Also if the .so library
          # ends with .so (no versioning), we don't need aliases.
          if self.suffix != 'so' or self.filename.endswith('.so'):
              return {}
          # If ltversion != soversion we create an soversion alias:
          # libfoo.so.0 -> libfoo.so.0.100.0
          if self.ltversion and self.ltversion != self.soversion:
              if not self.soversion:
                  # This is done in self.process_kwargs()
                  raise AssertionError('BUG: If library version is defined, soversion must have been defined')
              alias_tpl = self.filename_tpl.replace('ltversion', 'soversion')
              ltversion_filename = alias_tpl.format(self)
              aliases[ltversion_filename] = self.filename
          else:
              ltversion_filename = self.filename
          # Unversioned alias: libfoo.so -> libfoo.so.0
          aliases[self.basic_filename_tpl.format(self)] = ltversion_filename
          return aliases
  
      def type_suffix(self):
          return "@sha"
  
  # A shared library that is meant to be used with dlopen rather than linking
  # into something else.
  class SharedModule(SharedLibrary):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          if 'version' in kwargs:
              raise MesonException('Shared modules must not specify the version kwarg.')
          if 'soversion' in kwargs:
              raise MesonException('Shared modules must not specify the soversion kwarg.')
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
  
  class CustomTarget(Target):
      known_kwargs = {'input': True,
                      'output': True,
                      'command': True,
                      'capture': False,
                      'install': True,
                      'install_dir': True,
                      'build_always': True,
                      'depends': True,
                      'depend_files': True,
                      'depfile': True,
                      'build_by_default': True,
                      }
  
      def __init__(self, name, subdir, kwargs, absolute_paths=False):
          super().__init__(name, subdir, False)
          self.dependencies = []
          self.extra_depends = []
          self.depend_files = [] # Files that this target depends on but are not on the command line.
          self.depfile = None
          self.process_kwargs(kwargs)
          self.extra_files = []
          # Whether to use absolute paths for all files on the commandline
          self.absolute_paths = absolute_paths
          unknowns = []
          for k in kwargs:
              if k not in CustomTarget.known_kwargs:
                  unknowns.append(k)
          if len(unknowns) > 0:
              mlog.warning('Unknown keyword arguments in target %s: %s' %
                           (self.name, ', '.join(unknowns)))
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
  
      def get_id(self):
          return self.name + self.type_suffix()
  
      def get_target_dependencies(self):
          deps = self.dependencies[:]
          deps += self.extra_depends
          for c in self.sources:
              if hasattr(c, 'held_object'):
                  c = c.held_object
              if isinstance(c, (BuildTarget, CustomTarget, GeneratedList)):
                  deps.append(c)
          return deps
  
      def flatten_command(self, cmd):
          if not isinstance(cmd, list):
              cmd = [cmd]
          final_cmd = []
          for c in cmd:
              if hasattr(c, 'held_object'):
                  c = c.held_object
              if isinstance(c, (str, File)):
                  final_cmd.append(c)
              elif isinstance(c, dependencies.ExternalProgram):
                  if not c.found():
                      m = 'Tried to use not-found external program {!r} in "command"'
                      raise InvalidArguments(m.format(c.name))
                  final_cmd += c.get_command()
              elif isinstance(c, (BuildTarget, CustomTarget)):
                  self.dependencies.append(c)
                  final_cmd.append(c)
              elif isinstance(c, list):
                  final_cmd += self.flatten_command(c)
              else:
                  raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
          return final_cmd
  
      def process_kwargs(self, kwargs):
          super().process_kwargs(kwargs)
          self.sources = kwargs.get('input', [])
          if not isinstance(self.sources, list):
              self.sources = [self.sources]
          if 'output' not in kwargs:
              raise InvalidArguments('Missing keyword argument "output".')
          self.output = kwargs['output']
          if not isinstance(self.output, list):
              self.output = [self.output]
          for i in self.output:
              if not(isinstance(i, str)):
                  raise InvalidArguments('Output argument not a string.')
              if '/' in i:
                  raise InvalidArguments('Output must not contain a path segment.')
          self.capture = kwargs.get('capture', False)
          if self.capture and len(self.output) != 1:
              raise InvalidArguments('Capturing can only output to a single file.')
          if 'command' not in kwargs:
              raise InvalidArguments('Missing keyword argument "command".')
          if 'depfile' in kwargs:
              depfile = kwargs['depfile']
              if not isinstance(depfile, str):
                  raise InvalidArguments('Depfile must be a string.')
              if os.path.split(depfile)[1] != depfile:
                  raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
              self.depfile = depfile
          self.command = self.flatten_command(kwargs['command'])
          if self.capture:
              for c in self.command:
                  if isinstance(c, str) and '@OUTPUT@' in c:
                      raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.')
          if 'install' in kwargs:
              self.install = kwargs['install']
              if not isinstance(self.install, bool):
                  raise InvalidArguments('"install" must be boolean.')
              if self.install:
                  if 'install_dir' not in kwargs:
                      raise InvalidArguments('"install_dir" not specified.')
                  self.install_dir = kwargs['install_dir']
                  if not(isinstance(self.install_dir, str)):
                      raise InvalidArguments('"install_dir" must be a string.')
          else:
              self.install = False
          self.build_always = kwargs.get('build_always', False)
          if not isinstance(self.build_always, bool):
              raise InvalidArguments('Argument build_always must be a boolean.')
          extra_deps = kwargs.get('depends', [])
          if not isinstance(extra_deps, list):
              extra_deps = [extra_deps]
          for ed in extra_deps:
              while hasattr(ed, 'held_object'):
                  ed = ed.held_object
              if not isinstance(ed, (CustomTarget, BuildTarget)):
                  raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library)')
              self.extra_depends.append(ed)
          depend_files = kwargs.get('depend_files', [])
          if not isinstance(depend_files, list):
              depend_files = [depend_files]
          for i in depend_files:
              if isinstance(i, (File, str)):
                  self.depend_files.append(i)
              else:
                  mlog.debug(i)
                  raise InvalidArguments('Unknown type {!r} in depend_files.'.format(type(i).__name__))
  
      def get_dependencies(self):
          return self.dependencies
  
      def should_install(self):
          return self.install
  
      def get_custom_install_dir(self):
          return self.install_dir
  
      def get_outputs(self):
          return self.output
  
      def get_filename(self):
          return self.output[0]
  
      def get_sources(self):
          return self.sources
  
      def get_generated_sources(self):
          return []
  
      def type_suffix(self):
          return "@cus"
  
  class RunTarget(Target):
      def __init__(self, name, command, args, dependencies, subdir):
          super().__init__(name, subdir, False)
          self.command = command
          self.args = args
          self.dependencies = dependencies
  
      def __repr__(self):
          repr_str = "<{0} {1}: {2}>"
          return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
  
      def get_id(self):
          return self.name + self.type_suffix()
  
      def get_dependencies(self):
          return self.dependencies
  
      def get_generated_sources(self):
          return []
  
      def get_sources(self):
          return []
  
      def should_install(self):
          return False
  
      def get_filename(self):
          return self.name
  
      def type_suffix(self):
          return "@run"
  
  class Jar(BuildTarget):
      def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
          super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
          for s in self.sources:
              if not s.endswith('.java'):
                  raise InvalidArguments('Jar source %s is not a java file.' % s)
          self.filename = self.name + '.jar'
          self.java_args = kwargs.get('java_args', [])
  
      def get_main_class(self):
          return self.main_class
  
      def type_suffix(self):
          return "@jar"
  
      def get_java_args(self):
          return self.java_args
  
  class ConfigureFile:
  
      def __init__(self, subdir, sourcename, targetname, configuration_data):
          self.subdir = subdir
          self.sourcename = sourcename
          self.targetname = targetname
          self.configuration_data = configuration_data
  
      def __repr__(self):
          repr_str = "<{0}: {1} -> {2}>"
          src = os.path.join(self.subdir, self.sourcename)
          dst = os.path.join(self.subdir, self.targetname)
          return repr_str.format(self.__class__.__name__, src, dst)
  
      def get_configuration_data(self):
          return self.configuration_data
  
      def get_subdir(self):
          return self.subdir
  
      def get_source_name(self):
          return self.sourcename
  
      def get_target_name(self):
          return self.targetname
  
  class ConfigurationData:
      def __init__(self):
          super().__init__()
          self.values = {}
  
      def __repr__(self):
          return repr(self.values)
  
      def __contains__(self, value):
          return value in self.values
  
      def get(self, name):
          return self.values[name] # (val, desc)
  
      def keys(self):
          return self.values.keys()
  
  # A bit poorly named, but this represents plain data files to copy
  # during install.
  class Data:
      def __init__(self, sources, install_dir, install_mode=None):
          self.sources = sources
          self.install_dir = install_dir
          self.install_mode = install_mode
          if not isinstance(self.sources, list):
              self.sources = [self.sources]
          for s in self.sources:
              assert(isinstance(s, File))
  
  class RunScript(dict):
      def __init__(self, script, args):
          super().__init__()
          assert(isinstance(script, list))
          assert(isinstance(args, list))
          self['exe'] = script
          self['args'] = args
  
  class TestSetup:
      def __init__(self, *, exe_wrapper=None, gdb=None, timeout_multiplier=None, env=None):
          self.exe_wrapper = exe_wrapper
          self.gdb = gdb
          self.timeout_multiplier = timeout_multiplier
          self.env = env
  
files: mesonbuild/build.py
files: # Copyright 2012-2015 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  """A library of random helper functionality."""
  
  import stat
  import platform, subprocess, operator, os, shutil, re
  
  from glob import glob
  
  class MesonException(Exception):
      '''Exceptions thrown by Meson'''
  
  class EnvironmentException(MesonException):
      '''Exceptions thrown while processing and creating the build environment'''
  
  class FileMode:
      # The first triad is for owner permissions, the second for group permissions,
      # and the third for others (everyone else).
      # For the 1st character:
      #  'r' means can read
      #  '-' means not allowed
      # For the 2nd character:
      #  'w' means can write
      #  '-' means not allowed
      # For the 3rd character:
      #  'x' means can execute
      #  's' means can execute and setuid/setgid is set (owner/group triads only)
      #  'S' means cannot execute and setuid/setgid is set (owner/group triads only)
      #  't' means can execute and sticky bit is set ("others" triads only)
      #  'T' means cannot execute and sticky bit is set ("others" triads only)
      #  '-' means none of these are allowed
      #
      # The meanings of 'rwx' perms is not obvious for directories; see:
      # https://www.hackinglinuxexposed.com/articles/20030424.html
      #
      # For information on this notation such as setuid/setgid/sticky bits, see:
      # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation
      symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms
                                        '[r-][w-][xsS-]' # Group perms
                                        '[r-][w-][xtT-]') # Others perms
  
      def __init__(self, perms=None, owner=None, group=None):
          self.perms_s = perms
          self.perms = self.perms_s_to_bits(perms)
          self.owner = owner
          self.group = group
  
      def __repr__(self):
          ret = '<FileMode: {!r} owner={} group={}'
          return ret.format(self.perms_s, self.owner, self.group)
  
      @classmethod
      def perms_s_to_bits(cls, perms_s):
          '''
          Does the opposite of stat.filemode(), converts strings of the form
          'rwxr-xr-x' to st_mode enums which can be passed to os.chmod()
          '''
          if perms_s is None:
              # No perms specified, we will not touch the permissions
              return -1
          eg = 'rwxr-xr-x'
          if not isinstance(perms_s, str):
              msg = 'Install perms must be a string. For example, {!r}'
              raise MesonException(msg.format(eg))
          if len(perms_s) != 9 or not cls.symbolic_perms_regex.match(perms_s):
              msg = 'File perms {!r} must be exactly 9 chars. For example, {!r}'
              raise MesonException(msg.format(perms_s, eg))
          perms = 0
          # Owner perms
          if perms_s[0] == 'r':
              perms |= stat.S_IRUSR
          if perms_s[1] == 'w':
              perms |= stat.S_IWUSR
          if perms_s[2] == 'x':
              perms |= stat.S_IXUSR
          elif perms_s[2] == 'S':
              perms |= stat.S_ISUID
          elif perms_s[2] == 's':
              perms |= stat.S_IXUSR
              perms |= stat.S_ISUID
          # Group perms
          if perms_s[3] == 'r':
              perms |= stat.S_IRGRP
          if perms_s[4] == 'w':
              perms |= stat.S_IWGRP
          if perms_s[5] == 'x':
              perms |= stat.S_IXGRP
          elif perms_s[5] == 'S':
              perms |= stat.S_ISGID
          elif perms_s[5] == 's':
              perms |= stat.S_IXGRP
              perms |= stat.S_ISGID
          # Others perms
          if perms_s[6] == 'r':
              perms |= stat.S_IROTH
          if perms_s[7] == 'w':
              perms |= stat.S_IWOTH
          if perms_s[8] == 'x':
              perms |= stat.S_IXOTH
          elif perms_s[8] == 'T':
              perms |= stat.S_ISVTX
          elif perms_s[8] == 't':
              perms |= stat.S_IXOTH
              perms |= stat.S_ISVTX
          return perms
  
  class File:
      def __init__(self, is_built, subdir, fname):
          self.is_built = is_built
          self.subdir = subdir
          self.fname = fname
  
      def __str__(self):
          return os.path.join(self.subdir, self.fname)
  
      def __repr__(self):
          ret = '<File: {0}'
          if not self.is_built:
              ret += ' (not built)'
          ret += '>'
          return ret.format(os.path.join(self.subdir, self.fname))
  
      @staticmethod
      def from_source_file(source_root, subdir, fname):
          if not os.path.isfile(os.path.join(source_root, subdir, fname)):
              raise MesonException('File %s does not exist.' % fname)
          return File(False, subdir, fname)
  
      @staticmethod
      def from_built_file(subdir, fname):
          return File(True, subdir, fname)
  
      @staticmethod
      def from_absolute_file(fname):
          return File(False, '', fname)
  
      def rel_to_builddir(self, build_to_src):
          if self.is_built:
              return os.path.join(self.subdir, self.fname)
          else:
              return os.path.join(build_to_src, self.subdir, self.fname)
  
      def absolute_path(self, srcdir, builddir):
          if self.is_built:
              return os.path.join(builddir, self.subdir, self.fname)
          else:
              return os.path.join(srcdir, self.subdir, self.fname)
  
      def endswith(self, ending):
          return self.fname.endswith(ending)
  
      def split(self, s):
          return self.fname.split(s)
  
      def __eq__(self, other):
          return (self.fname, self.subdir, self.is_built) == (other.fname, other.subdir, other.is_built)
  
      def __hash__(self):
          return hash((self.fname, self.subdir, self.is_built))
  
      def relative_name(self):
          return os.path.join(self.subdir, self.fname)
  
  def get_compiler_for_source(compilers, src):
      for comp in compilers:
          if comp.can_compile(src):
              return comp
      raise RuntimeError('No specified compiler can handle file {!s}'.format(src))
  
  def classify_unity_sources(compilers, sources):
      compsrclist = {}
      for src in sources:
          comp = get_compiler_for_source(compilers, src)
          if comp not in compsrclist:
              compsrclist[comp] = [src]
          else:
              compsrclist[comp].append(src)
      return compsrclist
  
  def flatten(item):
      if not isinstance(item, list):
          return item
      result = []
      for i in item:
          if isinstance(i, list):
              result += flatten(i)
          else:
              result.append(i)
      return result
  
  def is_osx():
      return platform.system().lower() == 'darwin'
  
  def is_linux():
      return platform.system().lower() == 'linux'
  
  def is_windows():
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_debianlike():
      return os.path.isfile('/etc/debian_version')
  
  def exe_exists(arglist):
      try:
          p = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          p.communicate()
          if p.returncode == 0:
              return True
      except FileNotFoundError:
          pass
      return False
  
  def detect_vcs(source_dir):
      vcs_systems = [
          dict(name = 'git',        cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'),
          dict(name = 'mercurial',  cmd = 'hg',  repo_dir = '.hg',  get_rev = 'hg id -i',               rev_regex = '(.*)', dep = '.hg/dirstate'),
          dict(name = 'subversion', cmd = 'svn', repo_dir = '.svn', get_rev = 'svn info',               rev_regex = 'Revision: (.*)', dep = '.svn/wc.db'),
          dict(name = 'bazaar',     cmd = 'bzr', repo_dir = '.bzr', get_rev = 'bzr revno',              rev_regex = '(.*)', dep = '.bzr'),
      ]
  
      segs = source_dir.replace('\\', '/').split('/')
      for i in range(len(segs), -1, -1):
          curdir = '/'.join(segs[:i])
          for vcs in vcs_systems:
              if os.path.isdir(os.path.join(curdir, vcs['repo_dir'])) and shutil.which(vcs['cmd']):
                  vcs['wc_dir'] = curdir
                  return vcs
      return None
  
  def grab_leading_numbers(vstr, strict=False):
      result = []
      for x in vstr.split('.'):
          try:
              result.append(int(x))
          except ValueError as e:
              if strict:
                  msg = 'Invalid version to compare against: {!r}; only ' \
                        'numeric digits separated by "." are allowed: ' + str(e)
                  raise MesonException(msg.format(vstr))
              break
      return result
  
  numpart = re.compile('[0-9.]+')
  
  def version_compare(vstr1, vstr2, strict=False):
      match = numpart.match(vstr1.strip())
      if match is None:
          msg = 'Uncomparable version string {!r}.'
          raise MesonException(msg.format(vstr1))
      vstr1 = match.group(0)
      if vstr2.startswith('>='):
          cmpop = operator.ge
          vstr2 = vstr2[2:]
      elif vstr2.startswith('<='):
          cmpop = operator.le
          vstr2 = vstr2[2:]
      elif vstr2.startswith('!='):
          cmpop = operator.ne
          vstr2 = vstr2[2:]
      elif vstr2.startswith('=='):
          cmpop = operator.eq
          vstr2 = vstr2[2:]
      elif vstr2.startswith('='):
          cmpop = operator.eq
          vstr2 = vstr2[1:]
      elif vstr2.startswith('>'):
          cmpop = operator.gt
          vstr2 = vstr2[1:]
      elif vstr2.startswith('<'):
          cmpop = operator.lt
          vstr2 = vstr2[1:]
      else:
          cmpop = operator.eq
      varr1 = grab_leading_numbers(vstr1, strict)
      varr2 = grab_leading_numbers(vstr2, strict)
      return cmpop(varr1, varr2)
  
  def version_compare_many(vstr1, conditions):
      if not isinstance(conditions, (list, tuple)):
          conditions = [conditions]
      found = []
      not_found = []
      for req in conditions:
          if not version_compare(vstr1, req, strict=True):
              not_found.append(req)
          else:
              found.append(req)
      return not_found == [], not_found, found
  
  def default_libdir():
      if is_debianlike():
          try:
              pc = subprocess.Popen(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.DEVNULL)
              (stdo, _) = pc.communicate()
              if pc.returncode == 0:
                  archpath = stdo.decode().strip()
                  return 'lib/' + archpath
          except Exception:
              pass
      if os.path.isdir('/usr/lib64') and not os.path.islink('/usr/lib64'):
          return 'lib64'
      return 'lib'
  
  def default_libexecdir():
      # There is no way to auto-detect this, so it must be set at build time
      return 'libexec'
  
  def default_prefix():
      return 'c:/' if is_windows() else '/usr/local'
  
  def get_library_dirs():
      if is_windows():
          return ['C:/mingw/lib'] # Fixme
      if is_osx():
          return ['/usr/lib'] # Fix me as well.
      # The following is probably Debian/Ubuntu specific.
      # /usr/local/lib is first because it contains stuff
      # installed by the sysadmin and is probably more up-to-date
      # than /usr/lib. If you feel that this search order is
      # problematic, please raise the issue on the mailing list.
      unixdirs = ['/usr/local/lib', '/usr/lib', '/lib']
      plat = subprocess.check_output(['uname', '-m']).decode().strip()
      # This is a terrible hack. I admit it and I'm really sorry.
      # I just don't know what the correct solution is.
      if plat == 'i686':
          plat = 'i386'
      if plat.startswith('arm'):
          plat = 'arm'
      unixdirs += glob('/usr/lib/' + plat + '*')
      if os.path.exists('/usr/lib64'):
          unixdirs.append('/usr/lib64')
      unixdirs += glob('/lib/' + plat + '*')
      if os.path.exists('/lib64'):
          unixdirs.append('/lib64')
      unixdirs += glob('/lib/' + plat + '*')
      return unixdirs
  
  
  def do_replacement(regex, line, confdata):
      match = re.search(regex, line)
      while match:
          varname = match.group(1)
          if varname in confdata.keys():
              (var, desc) = confdata.get(varname)
              if isinstance(var, str):
                  pass
              elif isinstance(var, int):
                  var = str(var)
              else:
                  raise RuntimeError('Tried to replace a variable with something other than a string or int.')
          else:
              var = ''
          line = line.replace('@' + varname + '@', var)
          match = re.search(regex, line)
      return line
  
  def do_mesondefine(line, confdata):
      arr = line.split()
      if len(arr) != 2:
          raise MesonException('#mesondefine does not contain exactly two tokens: %s', line.strip())
      varname = arr[1]
      try:
          (v, desc) = confdata.get(varname)
      except KeyError:
          return '/* #undef %s */\n' % varname
      if isinstance(v, bool):
          if v:
              return '#define %s\n' % varname
          else:
              return '#undef %s\n' % varname
      elif isinstance(v, int):
          return '#define %s %d\n' % (varname, v)
      elif isinstance(v, str):
          return '#define %s %s\n' % (varname, v)
      else:
          raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname)
  
  
  def do_conf_file(src, dst, confdata):
      try:
          with open(src, encoding='utf-8') as f:
              data = f.readlines()
      except Exception as e:
          raise MesonException('Could not read input file %s: %s' % (src, str(e)))
      # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define
      # Also allow escaping '@' with '\@'
      regex = re.compile(r'[^\\]?@([-a-zA-Z0-9_]+)@')
      result = []
      for line in data:
          if line.startswith('#mesondefine'):
              line = do_mesondefine(line, confdata)
          else:
              line = do_replacement(regex, line, confdata)
          result.append(line)
      dst_tmp = dst + '~'
      with open(dst_tmp, 'w', encoding='utf-8') as f:
          f.writelines(result)
      shutil.copymode(src, dst_tmp)
      replace_if_different(dst, dst_tmp)
  
  def dump_conf_header(ofilename, cdata):
      with open(ofilename, 'w', encoding='utf-8') as ofile:
          ofile.write('''/*
   * Autogenerated by the Meson build system.
   * Do not edit, your changes will be lost.
   */
  
  #pragma once
  
  ''')
          for k in sorted(cdata.keys()):
              (v, desc) = cdata.get(k)
              if desc:
                  ofile.write('/* %s */\n' % desc)
              if isinstance(v, bool):
                  if v:
                      ofile.write('#define %s\n\n' % k)
                  else:
                      ofile.write('#undef %s\n\n' % k)
              elif isinstance(v, (int, str)):
                  ofile.write('#define %s %s\n\n' % (k, v))
              else:
                  raise MesonException('Unknown data type in configuration file entry: ' + k)
  
  def replace_if_different(dst, dst_tmp):
      # If contents are identical, don't touch the file to prevent
      # unnecessary rebuilds.
      different = True
      try:
          with open(dst, 'r') as f1, open(dst_tmp, 'r') as f2:
              if f1.read() == f2.read():
                  different = False
      except FileNotFoundError:
          pass
      if different:
          os.replace(dst_tmp, dst)
      else:
          os.unlink(dst_tmp)
  
  def stringintlistify(item):
      if isinstance(item, (str, int)):
          item = [item]
      if not isinstance(item, list):
          raise MesonException('Item must be a list, a string, or an int')
      for i in item:
          if not isinstance(i, (str, int, type(None))):
              raise MesonException('List item must be a string or an int')
      return item
  
  def stringlistify(item):
      if isinstance(item, str):
          item = [item]
      if not isinstance(item, list):
          raise MesonException('Item is not a list')
      for i in item:
          if not isinstance(i, str):
              raise MesonException('List item not a string.')
      return item
  
  def expand_arguments(args):
      expended_args = []
      for arg in args:
          if not arg.startswith('@'):
              expended_args.append(arg)
              continue
  
          args_file = arg[1:]
          try:
              with open(args_file) as f:
                  extended_args = f.read().split()
              expended_args += extended_args
          except Exception as e:
              print('Error expanding command line arguments, %s not found' % args_file)
              print(e)
              return None
      return expended_args
  
  def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs):
      p = subprocess.Popen(args, universal_newlines=True,
                           stdout=subprocess.PIPE,
                           stderr=stderr, **kwargs)
      o, e = p.communicate(write)
      return p, o, e
  
  def commonpath(paths):
      '''
      For use on Python 3.4 where os.path.commonpath is not available.
      We currently use it everywhere so this receives enough testing.
      '''
      # XXX: Replace me with os.path.commonpath when we start requiring Python 3.5
      import pathlib
      if not paths:
          raise ValueError('arg is an empty sequence')
      common = pathlib.PurePath(paths[0])
      for path in paths[1:]:
          new = []
          path = pathlib.PurePath(path)
          for c, p in zip(common.parts, path.parts):
              if c != p:
                  break
              new.append(c)
          # Don't convert '' into '.'
          if not new:
              common = ''
              break
          new = os.path.join(*new)
          common = pathlib.PurePath(new)
      return str(common)
  
  def iter_regexin_iter(regexiter, initer):
      '''
      Takes each regular expression in @regexiter and tries to search for it in
      every item in @initer. If there is a match, returns that match.
      Else returns False.
      '''
      for regex in regexiter:
          for ii in initer:
              if not isinstance(ii, str):
                  continue
              match = re.search(regex, ii)
              if match:
                  return match.group()
      return False
  
  def _substitute_values_check_errors(command, values):
      # Error checking
      inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@')
      outregex = ('@OUTPUT([0-9]+)?@', '@OUTDIR@')
      if '@INPUT@' not in values:
          # Error out if any input-derived templates are present in the command
          match = iter_regexin_iter(inregex, command)
          if match:
              m = 'Command cannot have {!r}, since no input files were specified'
              raise MesonException(m.format(match))
      else:
          if len(values['@INPUT@']) > 1:
              # Error out if @PLAINNAME@ or @BASENAME@ is present in the command
              match = iter_regexin_iter(inregex[1:], command)
              if match:
                  raise MesonException('Command cannot have {!r} when there is '
                                       'more than one input file'.format(match))
          # Error out if an invalid @INPUTnn@ template was specified
          for each in command:
              if not isinstance(each, str):
                  continue
              match = re.search(inregex[0], each)
              if match and match.group() not in values:
                  m = 'Command cannot have {!r} since there are only {!r} inputs'
                  raise MesonException(m.format(match.group(), len(values['@INPUT@'])))
      if '@OUTPUT@' not in values:
          # Error out if any output-derived templates are present in the command
          match = iter_regexin_iter(outregex, command)
          if match:
              m = 'Command cannot have {!r} since there are no outputs'
              raise MesonException(m.format(match))
      else:
          # Error out if an invalid @OUTPUTnn@ template was specified
          for each in command:
              if not isinstance(each, str):
                  continue
              match = re.search(outregex[0], each)
              if match and match.group() not in values:
                  m = 'Command cannot have {!r} since there are only {!r} outputs'
                  raise MesonException(m.format(match.group(), len(values['@OUTPUT@'])))
  
  def substitute_values(command, values):
      '''
      Substitute the template strings in the @values dict into the list of
      strings @command and return a new list. For a full list of the templates,
      see get_filenames_templates_dict()
  
      If multiple inputs/outputs are given in the @values dictionary, we
      substitute @INPUT@ and @OUTPUT@ only if they are the entire string, not
      just a part of it, and in that case we substitute *all* of them.
      '''
      # Error checking
      _substitute_values_check_errors(command, values)
      # Substitution
      outcmd = []
      for vv in command:
          if not isinstance(vv, str):
              outcmd.append(vv)
          elif '@INPUT@' in vv:
              inputs = values['@INPUT@']
              if vv == '@INPUT@':
                  outcmd += inputs
              elif len(inputs) == 1:
                  outcmd.append(vv.replace('@INPUT@', inputs[0]))
              else:
                  raise MesonException("Command has '@INPUT@' as part of a "
                                       "string and more than one input file")
          elif '@OUTPUT@' in vv:
              outputs = values['@OUTPUT@']
              if vv == '@OUTPUT@':
                  outcmd += outputs
              elif len(outputs) == 1:
                  outcmd.append(vv.replace('@OUTPUT@', outputs[0]))
              else:
                  raise MesonException("Command has '@OUTPUT@' as part of a "
                                       "string and more than one output file")
          # Append values that are exactly a template string.
          # This is faster than a string replace.
          elif vv in values:
              outcmd.append(values[vv])
          # Substitute everything else with replacement
          else:
              for key, value in values.items():
                  if key in ('@INPUT@', '@OUTPUT@'):
                      # Already done above
                      continue
                  vv = vv.replace(key, value)
              outcmd.append(vv)
      return outcmd
  
  def get_filenames_templates_dict(inputs, outputs):
      '''
      Create a dictionary with template strings as keys and values as values for
      the following templates:
  
      @INPUT@  - the full path to one or more input files, from @inputs
      @OUTPUT@ - the full path to one or more output files, from @outputs
      @OUTDIR@ - the full path to the directory containing the output files
  
      If there is only one input file, the following keys are also created:
  
      @PLAINNAME@ - the filename of the input file
      @BASENAME@ - the filename of the input file with the extension removed
  
      If there is more than one input file, the following keys are also created:
  
      @INPUT0@, @INPUT1@, ... one for each input file
  
      If there is more than one output file, the following keys are also created:
  
      @OUTPUT0@, @OUTPUT1@, ... one for each output file
      '''
      values = {}
      # Gather values derived from the input
      if inputs:
          # We want to substitute all the inputs.
          values['@INPUT@'] = inputs
          for (ii, vv) in enumerate(inputs):
              # Write out @INPUT0@, @INPUT1@, ...
              values['@INPUT{}@'.format(ii)] = vv
          if len(inputs) == 1:
              # Just one value, substitute @PLAINNAME@ and @BASENAME@
              values['@PLAINNAME@'] = plain = os.path.split(inputs[0])[1]
              values['@BASENAME@'] = os.path.splitext(plain)[0]
      if outputs:
          # Gather values derived from the outputs, similar to above.
          values['@OUTPUT@'] = outputs
          for (ii, vv) in enumerate(outputs):
              values['@OUTPUT{}@'.format(ii)] = vv
          # Outdir should be the same for all outputs
          values['@OUTDIR@'] = os.path.split(outputs[0])[0]
          # Many external programs fail on empty arguments.
          if values['@OUTDIR@'] == '':
              values['@OUTDIR@'] = '.'
      return values
  
files: mesonbuild/mesonlib.py
files: # Copyright 2012-2015 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  """A library of random helper functionality."""
  
  import stat
  import platform, subprocess, operator, os, shutil, re
  
  from glob import glob
  
  class MesonException(Exception):
      '''Exceptions thrown by Meson'''
  
  class EnvironmentException(MesonException):
      '''Exceptions thrown while processing and creating the build environment'''
  
  class FileMode:
      # The first triad is for owner permissions, the second for group permissions,
      # and the third for others (everyone else).
      # For the 1st character:
      #  'r' means can read
      #  '-' means not allowed
      # For the 2nd character:
      #  'w' means can write
      #  '-' means not allowed
      # For the 3rd character:
      #  'x' means can execute
      #  's' means can execute and setuid/setgid is set (owner/group triads only)
      #  'S' means cannot execute and setuid/setgid is set (owner/group triads only)
      #  't' means can execute and sticky bit is set ("others" triads only)
      #  'T' means cannot execute and sticky bit is set ("others" triads only)
      #  '-' means none of these are allowed
      #
      # The meanings of 'rwx' perms is not obvious for directories; see:
      # https://www.hackinglinuxexposed.com/articles/20030424.html
      #
      # For information on this notation such as setuid/setgid/sticky bits, see:
      # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation
      symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms
                                        '[r-][w-][xsS-]' # Group perms
                                        '[r-][w-][xtT-]') # Others perms
  
      def __init__(self, perms=None, owner=None, group=None):
          self.perms_s = perms
          self.perms = self.perms_s_to_bits(perms)
          self.owner = owner
          self.group = group
  
      def __repr__(self):
          ret = '<FileMode: {!r} owner={} group={}'
          return ret.format(self.perms_s, self.owner, self.group)
  
      @classmethod
      def perms_s_to_bits(cls, perms_s):
          '''
          Does the opposite of stat.filemode(), converts strings of the form
          'rwxr-xr-x' to st_mode enums which can be passed to os.chmod()
          '''
          if perms_s is None:
              # No perms specified, we will not touch the permissions
              return -1
          eg = 'rwxr-xr-x'
          if not isinstance(perms_s, str):
              msg = 'Install perms must be a string. For example, {!r}'
              raise MesonException(msg.format(eg))
          if len(perms_s) != 9 or not cls.symbolic_perms_regex.match(perms_s):
              msg = 'File perms {!r} must be exactly 9 chars. For example, {!r}'
              raise MesonException(msg.format(perms_s, eg))
          perms = 0
          # Owner perms
          if perms_s[0] == 'r':
              perms |= stat.S_IRUSR
          if perms_s[1] == 'w':
              perms |= stat.S_IWUSR
          if perms_s[2] == 'x':
              perms |= stat.S_IXUSR
          elif perms_s[2] == 'S':
              perms |= stat.S_ISUID
          elif perms_s[2] == 's':
              perms |= stat.S_IXUSR
              perms |= stat.S_ISUID
          # Group perms
          if perms_s[3] == 'r':
              perms |= stat.S_IRGRP
          if perms_s[4] == 'w':
              perms |= stat.S_IWGRP
          if perms_s[5] == 'x':
              perms |= stat.S_IXGRP
          elif perms_s[5] == 'S':
              perms |= stat.S_ISGID
          elif perms_s[5] == 's':
              perms |= stat.S_IXGRP
              perms |= stat.S_ISGID
          # Others perms
          if perms_s[6] == 'r':
              perms |= stat.S_IROTH
          if perms_s[7] == 'w':
              perms |= stat.S_IWOTH
          if perms_s[8] == 'x':
              perms |= stat.S_IXOTH
          elif perms_s[8] == 'T':
              perms |= stat.S_ISVTX
          elif perms_s[8] == 't':
              perms |= stat.S_IXOTH
              perms |= stat.S_ISVTX
          return perms
  
  class File:
      def __init__(self, is_built, subdir, fname):
          self.is_built = is_built
          self.subdir = subdir
          self.fname = fname
  
      def __str__(self):
          return os.path.join(self.subdir, self.fname)
  
      def __repr__(self):
          ret = '<File: {0}'
          if not self.is_built:
              ret += ' (not built)'
          ret += '>'
          return ret.format(os.path.join(self.subdir, self.fname))
  
      @staticmethod
      def from_source_file(source_root, subdir, fname):
          if not os.path.isfile(os.path.join(source_root, subdir, fname)):
              raise MesonException('File %s does not exist.' % fname)
          return File(False, subdir, fname)
  
      @staticmethod
      def from_built_file(subdir, fname):
          return File(True, subdir, fname)
  
      @staticmethod
      def from_absolute_file(fname):
          return File(False, '', fname)
  
      def rel_to_builddir(self, build_to_src):
          if self.is_built:
              return os.path.join(self.subdir, self.fname)
          else:
              return os.path.join(build_to_src, self.subdir, self.fname)
  
      def absolute_path(self, srcdir, builddir):
          if self.is_built:
              return os.path.join(builddir, self.subdir, self.fname)
          else:
              return os.path.join(srcdir, self.subdir, self.fname)
  
      def endswith(self, ending):
          return self.fname.endswith(ending)
  
      def split(self, s):
          return self.fname.split(s)
  
      def __eq__(self, other):
          return (self.fname, self.subdir, self.is_built) == (other.fname, other.subdir, other.is_built)
  
      def __hash__(self):
          return hash((self.fname, self.subdir, self.is_built))
  
      def relative_name(self):
          return os.path.join(self.subdir, self.fname)
  
  def get_compiler_for_source(compilers, src):
      for comp in compilers:
          if comp.can_compile(src):
              return comp
      raise RuntimeError('No specified compiler can handle file {!s}'.format(src))
  
  def classify_unity_sources(compilers, sources):
      compsrclist = {}
      for src in sources:
          comp = get_compiler_for_source(compilers, src)
          if comp not in compsrclist:
              compsrclist[comp] = [src]
          else:
              compsrclist[comp].append(src)
      return compsrclist
  
  def flatten(item):
      if not isinstance(item, list):
          return item
      result = []
      for i in item:
          if isinstance(i, list):
              result += flatten(i)
          else:
              result.append(i)
      return result
  
  def is_osx():
      return platform.system().lower() == 'darwin'
  
  def is_linux():
      return platform.system().lower() == 'linux'
  
  def is_windows():
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_debianlike():
      return os.path.isfile('/etc/debian_version')
  
  def exe_exists(arglist):
      try:
          p = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          p.communicate()
          if p.returncode == 0:
              return True
      except FileNotFoundError:
          pass
      return False
  
  def detect_vcs(source_dir):
      vcs_systems = [
          dict(name = 'git',        cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'),
          dict(name = 'mercurial',  cmd = 'hg',  repo_dir = '.hg',  get_rev = 'hg id -i',               rev_regex = '(.*)', dep = '.hg/dirstate'),
          dict(name = 'subversion', cmd = 'svn', repo_dir = '.svn', get_rev = 'svn info',               rev_regex = 'Revision: (.*)', dep = '.svn/wc.db'),
          dict(name = 'bazaar',     cmd = 'bzr', repo_dir = '.bzr', get_rev = 'bzr revno',              rev_regex = '(.*)', dep = '.bzr'),
      ]
  
      segs = source_dir.replace('\\', '/').split('/')
      for i in range(len(segs), -1, -1):
          curdir = '/'.join(segs[:i])
          for vcs in vcs_systems:
              if os.path.isdir(os.path.join(curdir, vcs['repo_dir'])) and shutil.which(vcs['cmd']):
                  vcs['wc_dir'] = curdir
                  return vcs
      return None
  
  def grab_leading_numbers(vstr, strict=False):
      result = []
      for x in vstr.split('.'):
          try:
              result.append(int(x))
          except ValueError as e:
              if strict:
                  msg = 'Invalid version to compare against: {!r}; only ' \
                        'numeric digits separated by "." are allowed: ' + str(e)
                  raise MesonException(msg.format(vstr))
              break
      return result
  
  numpart = re.compile('[0-9.]+')
  
  def version_compare(vstr1, vstr2, strict=False):
      match = numpart.match(vstr1.strip())
      if match is None:
          msg = 'Uncomparable version string {!r}.'
          raise MesonException(msg.format(vstr1))
      vstr1 = match.group(0)
      if vstr2.startswith('>='):
          cmpop = operator.ge
          vstr2 = vstr2[2:]
      elif vstr2.startswith('<='):
          cmpop = operator.le
          vstr2 = vstr2[2:]
      elif vstr2.startswith('!='):
          cmpop = operator.ne
          vstr2 = vstr2[2:]
      elif vstr2.startswith('=='):
          cmpop = operator.eq
          vstr2 = vstr2[2:]
      elif vstr2.startswith('='):
          cmpop = operator.eq
          vstr2 = vstr2[1:]
      elif vstr2.startswith('>'):
          cmpop = operator.gt
          vstr2 = vstr2[1:]
      elif vstr2.startswith('<'):
          cmpop = operator.lt
          vstr2 = vstr2[1:]
      else:
          cmpop = operator.eq
      varr1 = grab_leading_numbers(vstr1, strict)
      varr2 = grab_leading_numbers(vstr2, strict)
      return cmpop(varr1, varr2)
  
  def version_compare_many(vstr1, conditions):
      if not isinstance(conditions, (list, tuple)):
          conditions = [conditions]
      found = []
      not_found = []
      for req in conditions:
          if not version_compare(vstr1, req, strict=True):
              not_found.append(req)
          else:
              found.append(req)
      return not_found == [], not_found, found
  
  def default_libdir():
      if is_debianlike():
          try:
              pc = subprocess.Popen(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.DEVNULL)
              (stdo, _) = pc.communicate()
              if pc.returncode == 0:
                  archpath = stdo.decode().strip()
                  return 'lib/' + archpath
          except Exception:
              pass
      if os.path.isdir('/usr/lib64') and not os.path.islink('/usr/lib64'):
          return 'lib64'
      return 'lib'
  
  def default_libexecdir():
      # There is no way to auto-detect this, so it must be set at build time
      return 'libexec'
  
  def default_prefix():
      return 'c:/' if is_windows() else '/usr/local'
  
  def get_library_dirs():
      if is_windows():
          return ['C:/mingw/lib'] # Fixme
      if is_osx():
          return ['/usr/lib'] # Fix me as well.
      # The following is probably Debian/Ubuntu specific.
      # /usr/local/lib is first because it contains stuff
      # installed by the sysadmin and is probably more up-to-date
      # than /usr/lib. If you feel that this search order is
      # problematic, please raise the issue on the mailing list.
      unixdirs = ['/usr/local/lib', '/usr/lib', '/lib']
      plat = subprocess.check_output(['uname', '-m']).decode().strip()
      # This is a terrible hack. I admit it and I'm really sorry.
      # I just don't know what the correct solution is.
      if plat == 'i686':
          plat = 'i386'
      if plat.startswith('arm'):
          plat = 'arm'
      unixdirs += glob('/usr/lib/' + plat + '*')
      if os.path.exists('/usr/lib64'):
          unixdirs.append('/usr/lib64')
      unixdirs += glob('/lib/' + plat + '*')
      if os.path.exists('/lib64'):
          unixdirs.append('/lib64')
      unixdirs += glob('/lib/' + plat + '*')
      return unixdirs
  
  
  def do_replacement(regex, line, confdata):
      match = re.search(regex, line)
      while match:
          varname = match.group(1)
          if varname in confdata.keys():
              (var, desc) = confdata.get(varname)
              if isinstance(var, str):
                  pass
              elif isinstance(var, int):
                  var = str(var)
              else:
                  raise RuntimeError('Tried to replace a variable with something other than a string or int.')
          else:
              var = ''
          line = line.replace('@' + varname + '@', var)
          match = re.search(regex, line)
      return line
  
  def do_mesondefine(line, confdata):
      arr = line.split()
      if len(arr) != 2:
          raise MesonException('#mesondefine does not contain exactly two tokens: %s', line.strip())
      varname = arr[1]
      try:
          (v, desc) = confdata.get(varname)
      except KeyError:
          return '/* #undef %s */\n' % varname
      if isinstance(v, bool):
          if v:
              return '#define %s\n' % varname
          else:
              return '#undef %s\n' % varname
      elif isinstance(v, int):
          return '#define %s %d\n' % (varname, v)
      elif isinstance(v, str):
          return '#define %s %s\n' % (varname, v)
      else:
          raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname)
  
  
  def do_conf_file(src, dst, confdata):
      try:
          with open(src, encoding='utf-8') as f:
              data = f.readlines()
      except Exception as e:
          raise MesonException('Could not read input file %s: %s' % (src, str(e)))
      # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define
      # Also allow escaping '@' with '\@'
      regex = re.compile(r'[^\\]?@([-a-zA-Z0-9_]+)@')
      result = []
      for line in data:
          if line.startswith('#mesondefine'):
              line = do_mesondefine(line, confdata)
          else:
              line = do_replacement(regex, line, confdata)
          result.append(line)
      dst_tmp = dst + '~'
      with open(dst_tmp, 'w', encoding='utf-8') as f:
          f.writelines(result)
      shutil.copymode(src, dst_tmp)
      replace_if_different(dst, dst_tmp)
  
  def dump_conf_header(ofilename, cdata):
      with open(ofilename, 'w', encoding='utf-8') as ofile:
          ofile.write('''/*
   * Autogenerated by the Meson build system.
   * Do not edit, your changes will be lost.
   */
  
  #pragma once
  
  ''')
          for k in sorted(cdata.keys()):
              (v, desc) = cdata.get(k)
              if desc:
                  ofile.write('/* %s */\n' % desc)
              if isinstance(v, bool):
                  if v:
                      ofile.write('#define %s\n\n' % k)
                  else:
                      ofile.write('#undef %s\n\n' % k)
              elif isinstance(v, (int, str)):
                  ofile.write('#define %s %s\n\n' % (k, v))
              else:
                  raise MesonException('Unknown data type in configuration file entry: ' + k)
  
  def replace_if_different(dst, dst_tmp):
      # If contents are identical, don't touch the file to prevent
      # unnecessary rebuilds.
      different = True
      try:
          with open(dst, 'r') as f1, open(dst_tmp, 'r') as f2:
              if f1.read() == f2.read():
                  different = False
      except FileNotFoundError:
          pass
      if different:
          os.replace(dst_tmp, dst)
      else:
          os.unlink(dst_tmp)
  
  def stringintlistify(item):
      if isinstance(item, (str, int)):
          item = [item]
      if not isinstance(item, list):
          raise MesonException('Item must be a list, a string, or an int')
      for i in item:
          if not isinstance(i, (str, int, type(None))):
              raise MesonException('List item must be a string or an int')
      return item
  
  def stringlistify(item):
      if isinstance(item, str):
          item = [item]
      if not isinstance(item, list):
          raise MesonException('Item is not a list')
      for i in item:
          if not isinstance(i, str):
              raise MesonException('List item not a string.')
      return item
  
  def expand_arguments(args):
      expended_args = []
      for arg in args:
          if not arg.startswith('@'):
              expended_args.append(arg)
              continue
  
          args_file = arg[1:]
          try:
              with open(args_file) as f:
                  extended_args = f.read().split()
              expended_args += extended_args
          except Exception as e:
              print('Error expanding command line arguments, %s not found' % args_file)
              print(e)
              return None
      return expended_args
  
  def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs):
      p = subprocess.Popen(args, universal_newlines=True,
                           stdout=subprocess.PIPE,
                           stderr=stderr, **kwargs)
      o, e = p.communicate(write)
      return p, o, e
  
  def commonpath(paths):
      '''
      For use on Python 3.4 where os.path.commonpath is not available.
      We currently use it everywhere so this receives enough testing.
      '''
      # XXX: Replace me with os.path.commonpath when we start requiring Python 3.5
      import pathlib
      if not paths:
          raise ValueError('arg is an empty sequence')
      common = pathlib.PurePath(paths[0])
      for path in paths[1:]:
          new = []
          path = pathlib.PurePath(path)
          for c, p in zip(common.parts, path.parts):
              if c != p:
                  break
              new.append(c)
          # Don't convert '' into '.'
          if not new:
              common = ''
              break
          new = os.path.join(*new)
          common = pathlib.PurePath(new)
      return str(common)
  
files: mesonbuild/mesonlib.py
files: #!/usr/bin/env python3
  # Copyright 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.
  
  import stat
  import shlex
  import subprocess
  import re, json
  import tempfile
  import pathlib
  import unittest, os, sys, shutil, time
  from glob import glob
  import mesonbuild.compilers
  import mesonbuild.environment
  import mesonbuild.mesonlib
  from mesonbuild.environment import detect_ninja, Environment
  from mesonbuild.dependencies import PkgConfigDependency
  
  def get_soname(fname):
      # HACK, fix to not use shell.
      raw_out = subprocess.check_output(['readelf', '-a', fname],
                                        universal_newlines=True)
      pattern = re.compile('soname: \[(.*?)\]')
      for line in raw_out.split('\n'):
          m = pattern.search(line)
          if m is not None:
              return m.group(1)
      raise RuntimeError('Could not determine soname:\n\n' + raw_out)
  
  def get_fake_options(prefix):
      import argparse
      opts = argparse.Namespace()
      opts.cross_file = None
      opts.prefix = prefix
      return opts
  
  class FakeEnvironment(object):
      def __init__(self):
          self.cross_info = None
  
      def is_cross_build(self):
          return False
  
  class InternalTests(unittest.TestCase):
  
      def test_version_number(self):
          searchfunc = mesonbuild.environment.search_version
          self.assertEqual(searchfunc('foobar 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('foobar 2016.10.28 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('2016.10.28 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('foobar 2016.10.128'), 'unknown version')
          self.assertEqual(searchfunc('2016.10.128'), 'unknown version')
  
      def test_mode_symbolic_to_bits(self):
          modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits
          self.assertEqual(modefunc('---------'), 0)
          self.assertEqual(modefunc('r--------'), stat.S_IRUSR)
          self.assertEqual(modefunc('---r-----'), stat.S_IRGRP)
          self.assertEqual(modefunc('------r--'), stat.S_IROTH)
          self.assertEqual(modefunc('-w-------'), stat.S_IWUSR)
          self.assertEqual(modefunc('----w----'), stat.S_IWGRP)
          self.assertEqual(modefunc('-------w-'), stat.S_IWOTH)
          self.assertEqual(modefunc('--x------'), stat.S_IXUSR)
          self.assertEqual(modefunc('-----x---'), stat.S_IXGRP)
          self.assertEqual(modefunc('--------x'), stat.S_IXOTH)
          self.assertEqual(modefunc('--S------'), stat.S_ISUID)
          self.assertEqual(modefunc('-----S---'), stat.S_ISGID)
          self.assertEqual(modefunc('--------T'), stat.S_ISVTX)
          self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR)
          self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP)
          self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH)
          self.assertEqual(modefunc('rwx------'), stat.S_IRWXU)
          self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG)
          self.assertEqual(modefunc('------rwx'), stat.S_IRWXO)
          # We could keep listing combinations exhaustively but that seems
          # tedious and pointless. Just test a few more.
          self.assertEqual(modefunc('rwxr-xr-x'),
                           stat.S_IRWXU |
                           stat.S_IRGRP | stat.S_IXGRP |
                           stat.S_IROTH | stat.S_IXOTH)
          self.assertEqual(modefunc('rw-r--r--'),
                           stat.S_IRUSR | stat.S_IWUSR |
                           stat.S_IRGRP |
                           stat.S_IROTH)
          self.assertEqual(modefunc('rwsr-x---'),
                           stat.S_IRWXU | stat.S_ISUID |
                           stat.S_IRGRP | stat.S_IXGRP)
  
      def test_compiler_args_class(self):
          cargsfunc = mesonbuild.compilers.CompilerArgs
          c = mesonbuild.environment.CCompiler([], 'fake', False)
          # Test that bad initialization fails
          self.assertRaises(TypeError, cargsfunc, [])
          self.assertRaises(TypeError, cargsfunc, [], [])
          self.assertRaises(TypeError, cargsfunc, c, [], [])
          # Test that empty initialization works
          a = cargsfunc(c)
          self.assertEqual(a, [])
          # Test that list initialization works
          a = cargsfunc(['-I.', '-I..'], c)
          self.assertEqual(a, ['-I.', '-I..'])
          # Test that there is no de-dup on initialization
          self.assertEqual(cargsfunc(['-I.', '-I.'], c), ['-I.', '-I.'])
  
          ## Test that appending works
          a.append('-I..')
          self.assertEqual(a, ['-I..', '-I.'])
          a.append('-O3')
          self.assertEqual(a, ['-I..', '-I.', '-O3'])
  
          ## Test that in-place addition works
          a += ['-O2', '-O2']
          self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
          # Test that removal works
          a.remove('-O2')
          self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
          # Test that de-dup happens on addition
          a += ['-Ifoo', '-Ifoo']
          self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
  
          # .extend() is just +=, so we don't test it
  
          ## Test that addition works
          # Test that adding a list with just one old arg works and yields the same array
          a = a + ['-Ifoo']
          self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
          # Test that adding a list with one arg new and one old works
          a = a + ['-Ifoo', '-Ibaz']
          self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
          # Test that adding args that must be prepended and appended works
          a = a + ['-Ibar', '-Wall']
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
  
          ## Test that reflected addition works
          # Test that adding to a list with just one old arg works and DOES NOT yield the same array
          a = ['-Ifoo'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
          # Test that adding to a list with just one new arg that is not pre-pended works
          a = ['-Werror'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
          # Test that adding to a list with two new args preserves the order
          a = ['-Ldir', '-Lbah'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
  
      def test_commonpath(self):
          from os.path import sep
          commonpath = mesonbuild.mesonlib.commonpath
          self.assertRaises(ValueError, commonpath, [])
          self.assertEqual(commonpath(['/usr', '/usr']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/usr/']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/./', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr//bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr/./bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr/local', '/usr/lib']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/bin']), sep)
          self.assertEqual(commonpath(['/usr', 'bin']), '')
          self.assertEqual(commonpath(['blam', 'bin']), '')
          prefix = '/some/path/to/prefix'
          libdir = '/some/path/to/prefix/libdir'
          self.assertEqual(commonpath([prefix, libdir]), str(pathlib.PurePath(prefix)))
  
      def test_string_templates_substitution(self):
          dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict
          substfunc = mesonbuild.mesonlib.substitute_values
          ME = mesonbuild.mesonlib.MesonException
  
          # Identity
          self.assertEqual(dictfunc([], []), {})
  
          # One input, no outputs
          inputs = ['bar/foo.c.in']
          outputs = []
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
               '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
          cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings']
          self.assertEqual(substfunc(cmd, d),
                           [inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:])
          cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
          self.assertEqual(substfunc(cmd, d),
                           inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
          cmd = ['@OUTPUT@']
          self.assertRaises(ME, substfunc, cmd, d)
  
          # One input, one output
          inputs = ['bar/foo.c.in']
          outputs = ['out.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
               '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@INPUT@.out', '@OUTPUT@', 'strings']
          self.assertEqual(substfunc(cmd, d),
                           [inputs[0] + '.out'] + outputs + cmd[2:])
          cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@']
          self.assertEqual(substfunc(cmd, d),
                           [inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs)
          cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
          self.assertEqual(substfunc(cmd, d),
                           inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
  
          # One input, one output with a subdir
          outputs = ['dir/out.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
               '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
          # Check dictionary
          self.assertEqual(ret, d)
  
          # Two inputs, no outputs
          inputs = ['bar/foo.c.in', 'baz/foo.c.in']
          outputs = []
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@INPUT@', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), inputs + cmd[1:])
          cmd = ['@INPUT0@.out', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
          cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings']
          self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
          cmd = ['@INPUT0@', '@INPUT1@', 'strings']
          self.assertEqual(substfunc(cmd, d), inputs + cmd[2:])
          # Many inputs, can't use @INPUT@ like this
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Too many inputs
          cmd = ['@PLAINNAME@']
          self.assertRaises(ME, substfunc, cmd, d)
          cmd = ['@BASENAME@']
          self.assertRaises(ME, substfunc, cmd, d)
          # No outputs
          cmd = ['@OUTPUT@']
          self.assertRaises(ME, substfunc, cmd, d)
          cmd = ['@OUTPUT0@']
          self.assertRaises(ME, substfunc, cmd, d)
          cmd = ['@OUTDIR@']
          self.assertRaises(ME, substfunc, cmd, d)
  
          # Two inputs, one output
          outputs = ['dir/out.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@OUTPUT@', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
          cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:])
          cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings']
          self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
          # Many inputs, can't use @INPUT@ like this
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
          # Two inputs, two outputs
          outputs = ['dir/out.c', 'dir/out2.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
               '@OUTDIR@': 'dir'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@OUTPUT@', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
          cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
          cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
          self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
          # Many inputs, can't use @INPUT@ like this
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Many outputs, can't use @OUTPUT@ like this
          cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
  
  class LinuxlikeTests(unittest.TestCase):
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          src_root = os.path.join(os.getcwd(), src_root)
          self.builddir = tempfile.mkdtemp()
          self.logdir = os.path.join(self.builddir, 'meson-logs')
          self.prefix = '/usr'
          self.libdir = os.path.join(self.prefix, 'lib')
          self.installdir = os.path.join(self.builddir, 'install')
          self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py')]
          self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')]
          self.mintro_command = [sys.executable, os.path.join(src_root, 'mesonintrospect.py')]
          self.mtest_command = [sys.executable, os.path.join(src_root, 'mesontest.py'), '-C', self.builddir]
          self.ninja_command = [detect_ninja(), '-C', self.builddir]
          self.common_test_dir = os.path.join(src_root, 'test cases/common')
          self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
          self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
          self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
          self.output = b''
          self.orig_env = os.environ.copy()
  
      def tearDown(self):
          shutil.rmtree(self.builddir)
          os.environ = self.orig_env
          super().tearDown()
  
      def _run(self, command):
          self.output += subprocess.check_output(command, stderr=subprocess.STDOUT,
                                                 env=os.environ.copy())
  
      def init(self, srcdir, extra_args=None):
          if extra_args is None:
              extra_args = []
          args = [srcdir, self.builddir,
                  '--prefix', self.prefix,
                  '--libdir', self.libdir]
          self._run(self.meson_command + args + extra_args)
          self.privatedir = os.path.join(self.builddir, 'meson-private')
  
      def build(self):
          self._run(self.ninja_command)
  
      def run_tests(self):
          self._run(self.ninja_command + ['test'])
  
      def install(self):
          os.environ['DESTDIR'] = self.installdir
          self._run(self.ninja_command + ['install'])
  
      def uninstall(self):
          self._run(self.ninja_command + ['uninstall'])
  
      def run_target(self, target):
          self.output += subprocess.check_output(self.ninja_command + [target])
  
      def setconf(self, arg):
          self._run(self.mconf_command + [arg, self.builddir])
  
      def wipe(self):
          shutil.rmtree(self.builddir)
  
      def get_compdb(self):
          with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
              return json.load(ifile)
  
      def get_meson_log(self):
          with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f:
              return f.readlines()
  
      def get_meson_log_compiler_checks(self):
          '''
          Fetch a list command-lines run by meson for compiler checks.
          Each command-line is returned as a list of arguments.
          '''
          log = self.get_meson_log()
          prefix = 'Command line:'
          cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
          return cmds
  
      def introspect(self, arg):
          out = subprocess.check_output(self.mintro_command + [arg, self.builddir],
                                        universal_newlines=True)
          return json.loads(out)
  
      def test_basic_soname(self):
          '''
          Test that the soname is set correctly for shared libraries. This can't
          be an ordinary test case because we need to run `readelf` and actually
          check the soname.
          https://github.com/mesonbuild/meson/issues/785
          '''
          testdir = os.path.join(self.common_test_dir, '4 shared')
          self.init(testdir)
          self.build()
          lib1 = os.path.join(self.builddir, 'libmylib.so')
          soname = get_soname(lib1)
          self.assertEqual(soname, 'libmylib.so')
  
      def test_custom_soname(self):
          '''
          Test that the soname is set correctly for shared libraries when
          a custom prefix and/or suffix is used. This can't be an ordinary test
          case because we need to run `readelf` and actually check the soname.
          https://github.com/mesonbuild/meson/issues/785
          '''
          testdir = os.path.join(self.common_test_dir, '27 library versions')
          self.init(testdir)
          self.build()
          lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix')
          soname = get_soname(lib1)
          self.assertEqual(soname, 'prefixsomelib.suffix')
  
      def test_pic(self):
          '''
          Test that -fPIC is correctly added to static libraries when b_staticpic
          is true and not when it is false. This can't be an ordinary test case
          because we need to inspect the compiler database.
          '''
          testdir = os.path.join(self.common_test_dir, '3 static')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertIn('-fPIC', compdb[0]['command'])
          # This is needed to increase the difference between build.ninja's
          # timestamp and coredata.dat's timestamp due to a Ninja bug.
          # https://github.com/ninja-build/ninja/issues/371
          time.sleep(1)
          self.setconf('-Db_staticpic=false')
          # Regenerate build
          self.build()
          compdb = self.get_compdb()
          self.assertNotIn('-fPIC', compdb[0]['command'])
  
      def test_pkgconfig_gen(self):
          '''
          Test that generated pkg-config files can be found and have the correct
          version and link args. This can't be an ordinary test case because we
          need to run pkg-config outside of a Meson build file.
          https://github.com/mesonbuild/meson/issues/889
          '''
          testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen')
          self.init(testdir)
          env = FakeEnvironment()
          kwargs = {'required': True, 'silent': True}
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          simple_dep = PkgConfigDependency('libfoo', env, kwargs)
          self.assertTrue(simple_dep.found())
          self.assertEqual(simple_dep.get_version(), '1.0')
          self.assertIn('-lfoo', simple_dep.get_link_args())
  
      def test_vala_c_warnings(self):
          '''
          Test that no warnings are emitted for C code generated by Vala. This
          can't be an ordinary test case because we need to inspect the compiler
          database.
          https://github.com/mesonbuild/meson/issues/864
          '''
          testdir = os.path.join(self.vala_test_dir, '5 target glib')
          self.init(testdir)
          compdb = self.get_compdb()
          vala_command = None
          c_command = None
          for each in compdb:
              if each['file'].endswith('GLib.Thread.c'):
                  vala_command = each['command']
              elif each['file'].endswith('retcode.c'):
                  c_command = each['command']
              else:
                  m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file'])
                  raise AssertionError(m)
          self.assertIsNotNone(vala_command)
          self.assertIsNotNone(c_command)
          # -w suppresses all warnings, should be there in Vala but not in C
          self.assertIn("'-w'", vala_command)
          self.assertNotIn("'-w'", c_command)
          # -Wall enables all warnings, should be there in C but not in Vala
          self.assertNotIn("'-Wall'", vala_command)
          self.assertIn("'-Wall'", c_command)
          # -Werror converts warnings to errors, should always be there since it's
          # injected by an unrelated piece of code and the project has werror=true
          self.assertIn("'-Werror'", vala_command)
          self.assertIn("'-Werror'", c_command)
  
      def test_static_compile_order(self):
          '''
          Test that the order of files in a compiler command-line while compiling
          and linking statically is deterministic. This can't be an ordinary test
          case because we need to inspect the compiler database.
          https://github.com/mesonbuild/meson/pull/951
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir)
          compdb = self.get_compdb()
          # Rules will get written out in this order
          self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
          self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
          self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
          self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
          # FIXME: We don't have access to the linker command
  
      def test_install_introspection(self):
          '''
          Tests that the Meson introspection API exposes install filenames correctly
          https://github.com/mesonbuild/meson/issues/829
          '''
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          intro = self.introspect('--targets')
          if intro[0]['type'] == 'executable':
              intro = intro[::-1]
          self.assertEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
          self.assertEqual(intro[1]['install_filename'], '/usr/bin/prog')
  
      def test_run_target_files_path(self):
          '''
          Test that run_targets are run from the correct directory
          https://github.com/mesonbuild/meson/issues/957
          '''
          testdir = os.path.join(self.common_test_dir, '58 run target')
          self.init(testdir)
          self.run_target('check_exists')
  
      def test_qt5dependency_qmake_detection(self):
          '''
          Test that qt5 detection with qmake works. This can't be an ordinary
          test case because it involves setting the environment.
          '''
          # Verify that qmake is for Qt5
          if not shutil.which('qmake-qt5'):
              if not shutil.which('qmake'):
                  raise unittest.SkipTest('QMake not found')
              # For some inexplicable reason qmake --version gives different
              # results when run from the command line vs invoked by Python.
              # Check for both cases in case this behaviour changes in the future.
              output = subprocess.getoutput(['qmake', '--version'])
              if 'Qt version 5' not in output and 'qt5' not in output:
                  raise unittest.SkipTest('Qmake found, but it is not for Qt 5.')
          # Disable pkg-config codepath and force searching with qmake/qmake-qt5
          os.environ['PKG_CONFIG_LIBDIR'] = self.builddir
          os.environ['PKG_CONFIG_PATH'] = self.builddir
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir)
          # Confirm that the dependency was found with qmake
          msg = 'Qt5 native `qmake-qt5` dependency (modules: Core) found: YES\n'
          msg2 = 'Qt5 native `qmake` dependency (modules: Core) found: YES\n'
          mesonlog = self.get_meson_log()
          self.assertTrue(msg in mesonlog or msg2 in mesonlog)
  
      def get_soname(self, fname):
          output = subprocess.check_output(['readelf', '-a', fname],
                                           universal_newlines=True)
          for line in output.split('\n'):
              if 'SONAME' in line:
                  return line.split('[')[1].split(']')[0]
          raise RuntimeError('Readelf gave no SONAME.')
  
      def _test_soname_impl(self, libpath, install):
          testdir = os.path.join(self.unit_test_dir, '1 soname')
          self.init(testdir)
          self.build()
          if install:
              self.install()
  
          # File without aliases set.
          nover = os.path.join(libpath, 'libnover.so')
          self.assertTrue(os.path.exists(nover))
          self.assertFalse(os.path.islink(nover))
          self.assertEqual(self.get_soname(nover), 'libnover.so')
          self.assertEqual(len(glob(nover[:-3] + '*')), 1)
  
          # File with version set
          verset = os.path.join(libpath, 'libverset.so')
          self.assertTrue(os.path.exists(verset + '.4.5.6'))
          self.assertEqual(os.readlink(verset), 'libverset.so.4')
          self.assertEqual(self.get_soname(verset), 'libverset.so.4')
          self.assertEqual(len(glob(verset[:-3] + '*')), 3)
  
          # File with soversion set
          soverset = os.path.join(libpath, 'libsoverset.so')
          self.assertTrue(os.path.exists(soverset + '.1.2.3'))
          self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(self.get_soname(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
  
          # File with version and soversion set to same values
          settosame = os.path.join(libpath, 'libsettosame.so')
          self.assertTrue(os.path.exists(settosame + '.7.8.9'))
          self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(self.get_soname(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
  
          # File with version and soversion set to different values
          bothset = os.path.join(libpath, 'libbothset.so')
          self.assertTrue(os.path.exists(bothset + '.1.2.3'))
          self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
          self.assertEqual(self.get_soname(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
  
      def test_soname(self):
          self._test_soname_impl(self.builddir, False)
  
      def test_installed_soname(self):
          self._test_soname_impl(self.installdir + self.libdir, True)
  
      def test_compiler_check_flags_order(self):
          '''
          Test that compiler check flags override all other flags. This can't be
          an ordinary test case because it needs the environment to be set.
          '''
          Oflag = '-O3'
          os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag
          testdir = os.path.join(self.common_test_dir, '43 has function')
          self.init(testdir)
          cmds = self.get_meson_log_compiler_checks()
          for cmd in cmds:
              if cmd[0] == 'ccache':
                  cmd = cmd[1:]
              # Verify that -I flags from the `args` kwarg are first
              # This is set in the '43 has function' test case
              self.assertEqual(cmd[1], '-I/tmp')
              # Verify that -O3 set via the environment is overriden by -O0
              Oargs = [arg for arg in cmd if arg.startswith('-O')]
              self.assertEqual(Oargs, [Oflag, '-O0'])
  
      def test_uninstall(self):
          exename = os.path.join(self.installdir, 'usr/bin/prog')
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          self.assertFalse(os.path.exists(exename))
          self.install()
          self.assertTrue(os.path.exists(exename))
          self.uninstall()
          self.assertFalse(os.path.exists(exename))
  
      def test_custom_target_exe_data_deterministic(self):
          testdir = os.path.join(self.common_test_dir, '117 custom target capture')
          self.init(testdir)
          meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.wipe()
          self.init(testdir)
          meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
  
      def test_testsetups(self):
          if not shutil.which('valgrind'):
                  raise unittest.SkipTest('Valgrind not installed.')
          testdir = os.path.join(self.unit_test_dir, '2 testsetups')
          self.init(testdir)
          self.build()
          self.run_tests()
          with open(os.path.join(self.logdir, 'testlog.txt')) as f:
              basic_log = f.read()
          self.assertRaises(subprocess.CalledProcessError,
                            self._run, self.mtest_command + ['--setup=valgrind'])
          with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
              vg_log = f.read()
          self.assertFalse('TEST_ENV is set' in basic_log)
          self.assertFalse('Memcheck' in basic_log)
          self.assertTrue('TEST_ENV is set' in vg_log)
          self.assertTrue('Memcheck' in vg_log)
  
      def assertFailedTestCount(self, failure_count, command):
          try:
              self._run(command)
              self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
          except subprocess.CalledProcessError as e:
              self.assertEqual(e.returncode, failure_count)
  
      def test_suite_selection(self):
          testdir = os.path.join(self.unit_test_dir, '4 suite selection')
          self.init(testdir)
          self.build()
  
          self.assertFailedTestCount(3, self.mtest_command)
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
          self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
  
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
  
      def _test_stds_impl(self, testdir, compiler, p):
          lang_std = p + '_std'
          # Check that all the listed -std=xxx options for this compiler work
          # just fine when used
          for v in compiler.get_options()[lang_std].choices:
              std_opt = '{}={}'.format(lang_std, v)
              self.init(testdir, ['-D' + std_opt])
              cmd = self.get_compdb()[0]['command']
              if v != 'none':
                  cmd_std = "'-std={}'".format(v)
                  self.assertIn(cmd_std, cmd)
              try:
                  self.build()
              except:
                  print('{} was {!r}'.format(lang_std, v))
                  raise
              self.wipe()
          # Check that an invalid std option in CFLAGS/CPPFLAGS fails
          # Needed because by default ICC ignores invalid options
          cmd_std = '-std=FAIL'
          env_flags = p.upper() + 'FLAGS'
          os.environ[env_flags] = cmd_std
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          qcmd_std = "'{}'".format(cmd_std)
          self.assertIn(qcmd_std, cmd)
          with self.assertRaises(subprocess.CalledProcessError,
                                 msg='{} should have failed'.format(qcmd_std)):
              self.build()
  
      def test_compiler_c_stds(self):
          '''
          Test that C stds specified for this compiler can all be used. Can't be
          an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          env = Environment(testdir, self.builddir, self.meson_command,
                            get_fake_options(self.prefix), [])
          cc = env.detect_c_compiler(False)
          self._test_stds_impl(testdir, cc, 'c')
  
      def test_compiler_cpp_stds(self):
          '''
          Test that C++ stds specified for this compiler can all be used. Can't
          be an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '2 cpp')
          env = Environment(testdir, self.builddir, self.meson_command,
                            get_fake_options(self.prefix), [])
          cpp = env.detect_cpp_compiler(False)
          self._test_stds_impl(testdir, cpp, 'cpp')
  
      def test_build_by_default(self):
          testdir = os.path.join(self.common_test_dir, '137 build by default')
          self.init(testdir)
          self.build()
          genfile = os.path.join(self.builddir, 'generated.dat')
          exe = os.path.join(self.builddir, 'fooprog')
          self.assertTrue(os.path.exists(genfile))
          self.assertFalse(os.path.exists(exe))
          self._run(self.ninja_command + ['fooprog'])
          self.assertTrue(os.path.exists(exe))
  
      def test_libdir_must_be_inside_prefix(self):
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          # libdir being inside prefix is ok
          args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
          self.init(testdir, args)
          self.wipe()
          # libdir not being inside prefix is not ok
          args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
          self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
          self.wipe()
          # libdir must be inside prefix even when set via mesonconf
          self.init(testdir)
          self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt')
  
      def test_installed_modes(self):
          '''
          Test that files installed by these tests have the correct permissions.
          Can't be an ordinary test because our installed_files.txt is very basic.
          '''
          # Test file modes
          testdir = os.path.join(self.common_test_dir, '12 data')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'etc', 'etcfile.dat')
          found_mode = stat.filemode(os.stat(f).st_mode)
          want_mode = 'rw------T'
          self.assertEqual(want_mode, found_mode[1:])
  
          f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-sr-x'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
              self.assertEqual(0, statf.st_gid)
  
          f = os.path.join(self.installdir, 'usr', 'share', 'progname',
                           'fileobject_datafile.dat')
          orig = os.path.join(testdir, 'fileobject_datafile.dat')
          statf = os.stat(f)
          statorig = os.stat(orig)
          found_mode = stat.filemode(statf.st_mode)
          orig_mode = stat.filemode(statorig.st_mode)
          self.assertEqual(orig_mode[1:], found_mode[1:])
          self.assertEqual(os.getuid(), statf.st_uid)
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_gid)
  
          self.wipe()
          # Test directory modes
          testdir = os.path.join(self.common_test_dir, '66 install subdir')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'usr', 'share', 'sub1')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-x--t'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
  
      def test_internal_include_order(self):
          testdir = os.path.join(self.common_test_dir, '138 include order')
          self.init(testdir)
          for cmd in self.get_compdb():
              if cmd['file'].endswith('/main.c'):
                  cmd = cmd['command']
                  break
          else:
              raise Exception('Could not find main.c command')
          incs = [a for a in shlex.split(cmd) if a.startswith("-I")]
          self.assertEqual(len(incs), 8)
          # target private dir
          self.assertEqual(incs[0], "-Isub4/someexe@exe")
          # target build subdir
          self.assertEqual(incs[1], "-Isub4")
          # target source subdir
          msg = "{!r} does not end with '/sub4'".format(incs[2])
          self.assertTrue(incs[2].endswith("/sub4"), msg)
          # include paths added via per-target c_args: ['-I'...]
          msg = "{!r} does not end with '/sub3'".format(incs[3])
          self.assertTrue(incs[3].endswith("/sub3"), msg)
          # target include_directories: build dir
          self.assertEqual(incs[4], "-Isub2")
          # target include_directories: source dir
          msg = "{!r} does not end with '/sub2'".format(incs[5])
          self.assertTrue(incs[5].endswith("/sub2"), msg)
          # target internal dependency include_directories: build dir
          self.assertEqual(incs[6], "-Isub1")
          # target internal dependency include_directories: source dir
          msg = "{!r} does not end with '/sub1'".format(incs[7])
          self.assertTrue(incs[7].endswith("/sub1"), msg)
  
  
  class RewriterTests(unittest.TestCase):
  
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.testroot = tempfile.mkdtemp()
          self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')]
          self.tmpdir = tempfile.mkdtemp()
          self.workdir = os.path.join(self.tmpdir, 'foo')
          self.test_dir = os.path.join(src_root, 'test cases/rewrite')
  
      def tearDown(self):
          shutil.rmtree(self.tmpdir)
  
      def read_contents(self, fname):
          with open(os.path.join(self.workdir, fname)) as f:
              return f.read()
  
      def check_effectively_same(self, mainfile, truth):
          mf = self.read_contents(mainfile)
          t = self.read_contents(truth)
          # Rewriting is not guaranteed to do a perfect job of
          # maintaining whitespace.
          self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
  
      def prime(self, dirname):
          shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
  
      def test_basic(self):
          self.prime('1 basic')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'removed.txt')
          subprocess.check_output(self.rewrite_command + ['add',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'added.txt')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'removed.txt')
  
      def test_subdir(self):
          self.prime('2 subdirs')
          top = self.read_contents('meson.build')
          s2 = self.read_contents('sub2/meson.build')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=something',
                                                          '--filename=second.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
          self.assertEqual(top, self.read_contents('meson.build'))
          self.assertEqual(s2, self.read_contents('sub2/meson.build'))
  
  
  if __name__ == '__main__':
      unittest.main()
  
files: run_unittests.py
files: #!/usr/bin/env python3
  # Copyright 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.
  
  import stat
  import shlex
  import subprocess
  import re, json
  import tempfile
  import pathlib
  import unittest, os, sys, shutil, time
  from glob import glob
  import mesonbuild.compilers
  import mesonbuild.environment
  import mesonbuild.mesonlib
  from mesonbuild.environment import detect_ninja, Environment
  from mesonbuild.dependencies import PkgConfigDependency
  
  def get_soname(fname):
      # HACK, fix to not use shell.
      raw_out = subprocess.check_output(['readelf', '-a', fname],
                                        universal_newlines=True)
      pattern = re.compile('soname: \[(.*?)\]')
      for line in raw_out.split('\n'):
          m = pattern.search(line)
          if m is not None:
              return m.group(1)
      raise RuntimeError('Could not determine soname:\n\n' + raw_out)
  
  def get_fake_options(prefix):
      import argparse
      opts = argparse.Namespace()
      opts.cross_file = None
      opts.prefix = prefix
      return opts
  
  class FakeEnvironment(object):
      def __init__(self):
          self.cross_info = None
  
      def is_cross_build(self):
          return False
  
  class InternalTests(unittest.TestCase):
  
      def test_version_number(self):
          searchfunc = mesonbuild.environment.search_version
          self.assertEqual(searchfunc('foobar 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('foobar 2016.10.28 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('2016.10.28 1.2.3'), '1.2.3')
          self.assertEqual(searchfunc('foobar 2016.10.128'), 'unknown version')
          self.assertEqual(searchfunc('2016.10.128'), 'unknown version')
  
      def test_mode_symbolic_to_bits(self):
          modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits
          self.assertEqual(modefunc('---------'), 0)
          self.assertEqual(modefunc('r--------'), stat.S_IRUSR)
          self.assertEqual(modefunc('---r-----'), stat.S_IRGRP)
          self.assertEqual(modefunc('------r--'), stat.S_IROTH)
          self.assertEqual(modefunc('-w-------'), stat.S_IWUSR)
          self.assertEqual(modefunc('----w----'), stat.S_IWGRP)
          self.assertEqual(modefunc('-------w-'), stat.S_IWOTH)
          self.assertEqual(modefunc('--x------'), stat.S_IXUSR)
          self.assertEqual(modefunc('-----x---'), stat.S_IXGRP)
          self.assertEqual(modefunc('--------x'), stat.S_IXOTH)
          self.assertEqual(modefunc('--S------'), stat.S_ISUID)
          self.assertEqual(modefunc('-----S---'), stat.S_ISGID)
          self.assertEqual(modefunc('--------T'), stat.S_ISVTX)
          self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR)
          self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP)
          self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH)
          self.assertEqual(modefunc('rwx------'), stat.S_IRWXU)
          self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG)
          self.assertEqual(modefunc('------rwx'), stat.S_IRWXO)
          # We could keep listing combinations exhaustively but that seems
          # tedious and pointless. Just test a few more.
          self.assertEqual(modefunc('rwxr-xr-x'),
                           stat.S_IRWXU |
                           stat.S_IRGRP | stat.S_IXGRP |
                           stat.S_IROTH | stat.S_IXOTH)
          self.assertEqual(modefunc('rw-r--r--'),
                           stat.S_IRUSR | stat.S_IWUSR |
                           stat.S_IRGRP |
                           stat.S_IROTH)
          self.assertEqual(modefunc('rwsr-x---'),
                           stat.S_IRWXU | stat.S_ISUID |
                           stat.S_IRGRP | stat.S_IXGRP)
  
      def test_compiler_args_class(self):
          cargsfunc = mesonbuild.compilers.CompilerArgs
          c = mesonbuild.environment.CCompiler([], 'fake', False)
          # Test that bad initialization fails
          self.assertRaises(TypeError, cargsfunc, [])
          self.assertRaises(TypeError, cargsfunc, [], [])
          self.assertRaises(TypeError, cargsfunc, c, [], [])
          # Test that empty initialization works
          a = cargsfunc(c)
          self.assertEqual(a, [])
          # Test that list initialization works
          a = cargsfunc(['-I.', '-I..'], c)
          self.assertEqual(a, ['-I.', '-I..'])
          # Test that there is no de-dup on initialization
          self.assertEqual(cargsfunc(['-I.', '-I.'], c), ['-I.', '-I.'])
  
          ## Test that appending works
          a.append('-I..')
          self.assertEqual(a, ['-I..', '-I.'])
          a.append('-O3')
          self.assertEqual(a, ['-I..', '-I.', '-O3'])
  
          ## Test that in-place addition works
          a += ['-O2', '-O2']
          self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
          # Test that removal works
          a.remove('-O2')
          self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
          # Test that de-dup happens on addition
          a += ['-Ifoo', '-Ifoo']
          self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
  
          # .extend() is just +=, so we don't test it
  
          ## Test that addition works
          # Test that adding a list with just one old arg works and yields the same array
          a = a + ['-Ifoo']
          self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
          # Test that adding a list with one arg new and one old works
          a = a + ['-Ifoo', '-Ibaz']
          self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
          # Test that adding args that must be prepended and appended works
          a = a + ['-Ibar', '-Wall']
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
  
          ## Test that reflected addition works
          # Test that adding to a list with just one old arg works and DOES NOT yield the same array
          a = ['-Ifoo'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
          # Test that adding to a list with just one new arg that is not pre-pended works
          a = ['-Werror'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
          # Test that adding to a list with two new args preserves the order
          a = ['-Ldir', '-Lbah'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
  
      def test_commonpath(self):
          from os.path import sep
          commonpath = mesonbuild.mesonlib.commonpath
          self.assertRaises(ValueError, commonpath, [])
          self.assertEqual(commonpath(['/usr', '/usr']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/usr/']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/./', '/usr/bin']), sep + 'usr')
          self.assertEqual(commonpath(['/usr/bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr//bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr/./bin', '/usr/bin']), sep + 'usr' + sep + 'bin')
          self.assertEqual(commonpath(['/usr/local', '/usr/lib']), sep + 'usr')
          self.assertEqual(commonpath(['/usr', '/bin']), sep)
          self.assertEqual(commonpath(['/usr', 'bin']), '')
          self.assertEqual(commonpath(['blam', 'bin']), '')
          prefix = '/some/path/to/prefix'
          libdir = '/some/path/to/prefix/libdir'
          self.assertEqual(commonpath([prefix, libdir]), str(pathlib.PurePath(prefix)))
  
  
  class LinuxlikeTests(unittest.TestCase):
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          src_root = os.path.join(os.getcwd(), src_root)
          self.builddir = tempfile.mkdtemp()
          self.logdir = os.path.join(self.builddir, 'meson-logs')
          self.prefix = '/usr'
          self.libdir = os.path.join(self.prefix, 'lib')
          self.installdir = os.path.join(self.builddir, 'install')
          self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py')]
          self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')]
          self.mintro_command = [sys.executable, os.path.join(src_root, 'mesonintrospect.py')]
          self.mtest_command = [sys.executable, os.path.join(src_root, 'mesontest.py'), '-C', self.builddir]
          self.ninja_command = [detect_ninja(), '-C', self.builddir]
          self.common_test_dir = os.path.join(src_root, 'test cases/common')
          self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
          self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
          self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
          self.output = b''
          self.orig_env = os.environ.copy()
  
      def tearDown(self):
          shutil.rmtree(self.builddir)
          os.environ = self.orig_env
          super().tearDown()
  
      def _run(self, command):
          self.output += subprocess.check_output(command, stderr=subprocess.STDOUT,
                                                 env=os.environ.copy())
  
      def init(self, srcdir, extra_args=None):
          if extra_args is None:
              extra_args = []
          args = [srcdir, self.builddir,
                  '--prefix', self.prefix,
                  '--libdir', self.libdir]
          self._run(self.meson_command + args + extra_args)
          self.privatedir = os.path.join(self.builddir, 'meson-private')
  
      def build(self):
          self._run(self.ninja_command)
  
      def run_tests(self):
          self._run(self.ninja_command + ['test'])
  
      def install(self):
          os.environ['DESTDIR'] = self.installdir
          self._run(self.ninja_command + ['install'])
  
      def uninstall(self):
          self._run(self.ninja_command + ['uninstall'])
  
      def run_target(self, target):
          self.output += subprocess.check_output(self.ninja_command + [target])
  
      def setconf(self, arg):
          self._run(self.mconf_command + [arg, self.builddir])
  
      def wipe(self):
          shutil.rmtree(self.builddir)
  
      def get_compdb(self):
          with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
              return json.load(ifile)
  
      def get_meson_log(self):
          with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f:
              return f.readlines()
  
      def get_meson_log_compiler_checks(self):
          '''
          Fetch a list command-lines run by meson for compiler checks.
          Each command-line is returned as a list of arguments.
          '''
          log = self.get_meson_log()
          prefix = 'Command line:'
          cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
          return cmds
  
      def introspect(self, arg):
          out = subprocess.check_output(self.mintro_command + [arg, self.builddir],
                                        universal_newlines=True)
          return json.loads(out)
  
      def test_basic_soname(self):
          '''
          Test that the soname is set correctly for shared libraries. This can't
          be an ordinary test case because we need to run `readelf` and actually
          check the soname.
          https://github.com/mesonbuild/meson/issues/785
          '''
          testdir = os.path.join(self.common_test_dir, '4 shared')
          self.init(testdir)
          self.build()
          lib1 = os.path.join(self.builddir, 'libmylib.so')
          soname = get_soname(lib1)
          self.assertEqual(soname, 'libmylib.so')
  
      def test_custom_soname(self):
          '''
          Test that the soname is set correctly for shared libraries when
          a custom prefix and/or suffix is used. This can't be an ordinary test
          case because we need to run `readelf` and actually check the soname.
          https://github.com/mesonbuild/meson/issues/785
          '''
          testdir = os.path.join(self.common_test_dir, '27 library versions')
          self.init(testdir)
          self.build()
          lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix')
          soname = get_soname(lib1)
          self.assertEqual(soname, 'prefixsomelib.suffix')
  
      def test_pic(self):
          '''
          Test that -fPIC is correctly added to static libraries when b_staticpic
          is true and not when it is false. This can't be an ordinary test case
          because we need to inspect the compiler database.
          '''
          testdir = os.path.join(self.common_test_dir, '3 static')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertIn('-fPIC', compdb[0]['command'])
          # This is needed to increase the difference between build.ninja's
          # timestamp and coredata.dat's timestamp due to a Ninja bug.
          # https://github.com/ninja-build/ninja/issues/371
          time.sleep(1)
          self.setconf('-Db_staticpic=false')
          # Regenerate build
          self.build()
          compdb = self.get_compdb()
          self.assertNotIn('-fPIC', compdb[0]['command'])
  
      def test_pkgconfig_gen(self):
          '''
          Test that generated pkg-config files can be found and have the correct
          version and link args. This can't be an ordinary test case because we
          need to run pkg-config outside of a Meson build file.
          https://github.com/mesonbuild/meson/issues/889
          '''
          testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen')
          self.init(testdir)
          env = FakeEnvironment()
          kwargs = {'required': True, 'silent': True}
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          simple_dep = PkgConfigDependency('libfoo', env, kwargs)
          self.assertTrue(simple_dep.found())
          self.assertEqual(simple_dep.get_version(), '1.0')
          self.assertIn('-lfoo', simple_dep.get_link_args())
  
      def test_vala_c_warnings(self):
          '''
          Test that no warnings are emitted for C code generated by Vala. This
          can't be an ordinary test case because we need to inspect the compiler
          database.
          https://github.com/mesonbuild/meson/issues/864
          '''
          testdir = os.path.join(self.vala_test_dir, '5 target glib')
          self.init(testdir)
          compdb = self.get_compdb()
          vala_command = None
          c_command = None
          for each in compdb:
              if each['file'].endswith('GLib.Thread.c'):
                  vala_command = each['command']
              elif each['file'].endswith('retcode.c'):
                  c_command = each['command']
              else:
                  m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file'])
                  raise AssertionError(m)
          self.assertIsNotNone(vala_command)
          self.assertIsNotNone(c_command)
          # -w suppresses all warnings, should be there in Vala but not in C
          self.assertIn("'-w'", vala_command)
          self.assertNotIn("'-w'", c_command)
          # -Wall enables all warnings, should be there in C but not in Vala
          self.assertNotIn("'-Wall'", vala_command)
          self.assertIn("'-Wall'", c_command)
          # -Werror converts warnings to errors, should always be there since it's
          # injected by an unrelated piece of code and the project has werror=true
          self.assertIn("'-Werror'", vala_command)
          self.assertIn("'-Werror'", c_command)
  
      def test_static_compile_order(self):
          '''
          Test that the order of files in a compiler command-line while compiling
          and linking statically is deterministic. This can't be an ordinary test
          case because we need to inspect the compiler database.
          https://github.com/mesonbuild/meson/pull/951
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir)
          compdb = self.get_compdb()
          # Rules will get written out in this order
          self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
          self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
          self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
          self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
          # FIXME: We don't have access to the linker command
  
      def test_install_introspection(self):
          '''
          Tests that the Meson introspection API exposes install filenames correctly
          https://github.com/mesonbuild/meson/issues/829
          '''
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          intro = self.introspect('--targets')
          if intro[0]['type'] == 'executable':
              intro = intro[::-1]
          self.assertEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
          self.assertEqual(intro[1]['install_filename'], '/usr/bin/prog')
  
      def test_run_target_files_path(self):
          '''
          Test that run_targets are run from the correct directory
          https://github.com/mesonbuild/meson/issues/957
          '''
          testdir = os.path.join(self.common_test_dir, '58 run target')
          self.init(testdir)
          self.run_target('check_exists')
  
      def test_qt5dependency_qmake_detection(self):
          '''
          Test that qt5 detection with qmake works. This can't be an ordinary
          test case because it involves setting the environment.
          '''
          # Verify that qmake is for Qt5
          if not shutil.which('qmake-qt5'):
              if not shutil.which('qmake'):
                  raise unittest.SkipTest('QMake not found')
              # For some inexplicable reason qmake --version gives different
              # results when run from the command line vs invoked by Python.
              # Check for both cases in case this behaviour changes in the future.
              output = subprocess.getoutput(['qmake', '--version'])
              if 'Qt version 5' not in output and 'qt5' not in output:
                  raise unittest.SkipTest('Qmake found, but it is not for Qt 5.')
          # Disable pkg-config codepath and force searching with qmake/qmake-qt5
          os.environ['PKG_CONFIG_LIBDIR'] = self.builddir
          os.environ['PKG_CONFIG_PATH'] = self.builddir
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir)
          # Confirm that the dependency was found with qmake
          msg = 'Qt5 native `qmake-qt5` dependency (modules: Core) found: YES\n'
          msg2 = 'Qt5 native `qmake` dependency (modules: Core) found: YES\n'
          mesonlog = self.get_meson_log()
          self.assertTrue(msg in mesonlog or msg2 in mesonlog)
  
      def get_soname(self, fname):
          output = subprocess.check_output(['readelf', '-a', fname],
                                           universal_newlines=True)
          for line in output.split('\n'):
              if 'SONAME' in line:
                  return line.split('[')[1].split(']')[0]
          raise RuntimeError('Readelf gave no SONAME.')
  
      def _test_soname_impl(self, libpath, install):
          testdir = os.path.join(self.unit_test_dir, '1 soname')
          self.init(testdir)
          self.build()
          if install:
              self.install()
  
          # File without aliases set.
          nover = os.path.join(libpath, 'libnover.so')
          self.assertTrue(os.path.exists(nover))
          self.assertFalse(os.path.islink(nover))
          self.assertEqual(self.get_soname(nover), 'libnover.so')
          self.assertEqual(len(glob(nover[:-3] + '*')), 1)
  
          # File with version set
          verset = os.path.join(libpath, 'libverset.so')
          self.assertTrue(os.path.exists(verset + '.4.5.6'))
          self.assertEqual(os.readlink(verset), 'libverset.so.4')
          self.assertEqual(self.get_soname(verset), 'libverset.so.4')
          self.assertEqual(len(glob(verset[:-3] + '*')), 3)
  
          # File with soversion set
          soverset = os.path.join(libpath, 'libsoverset.so')
          self.assertTrue(os.path.exists(soverset + '.1.2.3'))
          self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(self.get_soname(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
  
          # File with version and soversion set to same values
          settosame = os.path.join(libpath, 'libsettosame.so')
          self.assertTrue(os.path.exists(settosame + '.7.8.9'))
          self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(self.get_soname(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
  
          # File with version and soversion set to different values
          bothset = os.path.join(libpath, 'libbothset.so')
          self.assertTrue(os.path.exists(bothset + '.1.2.3'))
          self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
          self.assertEqual(self.get_soname(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
  
      def test_soname(self):
          self._test_soname_impl(self.builddir, False)
  
      def test_installed_soname(self):
          self._test_soname_impl(self.installdir + self.libdir, True)
  
      def test_compiler_check_flags_order(self):
          '''
          Test that compiler check flags override all other flags. This can't be
          an ordinary test case because it needs the environment to be set.
          '''
          Oflag = '-O3'
          os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag
          testdir = os.path.join(self.common_test_dir, '43 has function')
          self.init(testdir)
          cmds = self.get_meson_log_compiler_checks()
          for cmd in cmds:
              if cmd[0] == 'ccache':
                  cmd = cmd[1:]
              # Verify that -I flags from the `args` kwarg are first
              # This is set in the '43 has function' test case
              self.assertEqual(cmd[1], '-I/tmp')
              # Verify that -O3 set via the environment is overriden by -O0
              Oargs = [arg for arg in cmd if arg.startswith('-O')]
              self.assertEqual(Oargs, [Oflag, '-O0'])
  
      def test_uninstall(self):
          exename = os.path.join(self.installdir, 'usr/bin/prog')
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          self.assertFalse(os.path.exists(exename))
          self.install()
          self.assertTrue(os.path.exists(exename))
          self.uninstall()
          self.assertFalse(os.path.exists(exename))
  
      def test_custom_target_exe_data_deterministic(self):
          testdir = os.path.join(self.common_test_dir, '117 custom target capture')
          self.init(testdir)
          meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.wipe()
          self.init(testdir)
          meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
  
      def test_testsetups(self):
          if not shutil.which('valgrind'):
                  raise unittest.SkipTest('Valgrind not installed.')
          testdir = os.path.join(self.unit_test_dir, '2 testsetups')
          self.init(testdir)
          self.build()
          self.run_tests()
          with open(os.path.join(self.logdir, 'testlog.txt')) as f:
              basic_log = f.read()
          self.assertRaises(subprocess.CalledProcessError,
                            self._run, self.mtest_command + ['--setup=valgrind'])
          with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
              vg_log = f.read()
          self.assertFalse('TEST_ENV is set' in basic_log)
          self.assertFalse('Memcheck' in basic_log)
          self.assertTrue('TEST_ENV is set' in vg_log)
          self.assertTrue('Memcheck' in vg_log)
  
      def assertFailedTestCount(self, failure_count, command):
          try:
              self._run(command)
              self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
          except subprocess.CalledProcessError as e:
              self.assertEqual(e.returncode, failure_count)
  
      def test_suite_selection(self):
          testdir = os.path.join(self.unit_test_dir, '4 suite selection')
          self.init(testdir)
          self.build()
  
          self.assertFailedTestCount(3, self.mtest_command)
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
          self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
  
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
  
      def _test_stds_impl(self, testdir, compiler, p):
          lang_std = p + '_std'
          # Check that all the listed -std=xxx options for this compiler work
          # just fine when used
          for v in compiler.get_options()[lang_std].choices:
              std_opt = '{}={}'.format(lang_std, v)
              self.init(testdir, ['-D' + std_opt])
              cmd = self.get_compdb()[0]['command']
              if v != 'none':
                  cmd_std = "'-std={}'".format(v)
                  self.assertIn(cmd_std, cmd)
              try:
                  self.build()
              except:
                  print('{} was {!r}'.format(lang_std, v))
                  raise
              self.wipe()
          # Check that an invalid std option in CFLAGS/CPPFLAGS fails
          # Needed because by default ICC ignores invalid options
          cmd_std = '-std=FAIL'
          env_flags = p.upper() + 'FLAGS'
          os.environ[env_flags] = cmd_std
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          qcmd_std = "'{}'".format(cmd_std)
          self.assertIn(qcmd_std, cmd)
          with self.assertRaises(subprocess.CalledProcessError,
                                 msg='{} should have failed'.format(qcmd_std)):
              self.build()
  
      def test_compiler_c_stds(self):
          '''
          Test that C stds specified for this compiler can all be used. Can't be
          an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          env = Environment(testdir, self.builddir, self.meson_command,
                            get_fake_options(self.prefix), [])
          cc = env.detect_c_compiler(False)
          self._test_stds_impl(testdir, cc, 'c')
  
      def test_compiler_cpp_stds(self):
          '''
          Test that C++ stds specified for this compiler can all be used. Can't
          be an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '2 cpp')
          env = Environment(testdir, self.builddir, self.meson_command,
                            get_fake_options(self.prefix), [])
          cpp = env.detect_cpp_compiler(False)
          self._test_stds_impl(testdir, cpp, 'cpp')
  
      def test_build_by_default(self):
          testdir = os.path.join(self.common_test_dir, '137 build by default')
          self.init(testdir)
          self.build()
          genfile = os.path.join(self.builddir, 'generated.dat')
          exe = os.path.join(self.builddir, 'fooprog')
          self.assertTrue(os.path.exists(genfile))
          self.assertFalse(os.path.exists(exe))
          self._run(self.ninja_command + ['fooprog'])
          self.assertTrue(os.path.exists(exe))
  
      def test_libdir_must_be_inside_prefix(self):
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          # libdir being inside prefix is ok
          args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
          self.init(testdir, args)
          self.wipe()
          # libdir not being inside prefix is not ok
          args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
          self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
          self.wipe()
          # libdir must be inside prefix even when set via mesonconf
          self.init(testdir)
          self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt')
  
      def test_installed_modes(self):
          '''
          Test that files installed by these tests have the correct permissions.
          Can't be an ordinary test because our installed_files.txt is very basic.
          '''
          # Test file modes
          testdir = os.path.join(self.common_test_dir, '12 data')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'etc', 'etcfile.dat')
          found_mode = stat.filemode(os.stat(f).st_mode)
          want_mode = 'rw------T'
          self.assertEqual(want_mode, found_mode[1:])
  
          f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-sr-x'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
              self.assertEqual(0, statf.st_gid)
  
          f = os.path.join(self.installdir, 'usr', 'share', 'progname',
                           'fileobject_datafile.dat')
          orig = os.path.join(testdir, 'fileobject_datafile.dat')
          statf = os.stat(f)
          statorig = os.stat(orig)
          found_mode = stat.filemode(statf.st_mode)
          orig_mode = stat.filemode(statorig.st_mode)
          self.assertEqual(orig_mode[1:], found_mode[1:])
          self.assertEqual(os.getuid(), statf.st_uid)
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_gid)
  
          self.wipe()
          # Test directory modes
          testdir = os.path.join(self.common_test_dir, '66 install subdir')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'usr', 'share', 'sub1')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-x--t'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
  
      def test_internal_include_order(self):
          testdir = os.path.join(self.common_test_dir, '138 include order')
          self.init(testdir)
          for cmd in self.get_compdb():
              if cmd['file'].endswith('/main.c'):
                  cmd = cmd['command']
                  break
          else:
              raise Exception('Could not find main.c command')
          incs = [a for a in shlex.split(cmd) if a.startswith("-I")]
          self.assertEqual(len(incs), 8)
          # target private dir
          self.assertEqual(incs[0], "-Isub4/someexe@exe")
          # target build subdir
          self.assertEqual(incs[1], "-Isub4")
          # target source subdir
          msg = "{!r} does not end with '/sub4'".format(incs[2])
          self.assertTrue(incs[2].endswith("/sub4"), msg)
          # include paths added via per-target c_args: ['-I'...]
          msg = "{!r} does not end with '/sub3'".format(incs[3])
          self.assertTrue(incs[3].endswith("/sub3"), msg)
          # target include_directories: build dir
          self.assertEqual(incs[4], "-Isub2")
          # target include_directories: source dir
          msg = "{!r} does not end with '/sub2'".format(incs[5])
          self.assertTrue(incs[5].endswith("/sub2"), msg)
          # target internal dependency include_directories: build dir
          self.assertEqual(incs[6], "-Isub1")
          # target internal dependency include_directories: source dir
          msg = "{!r} does not end with '/sub1'".format(incs[7])
          self.assertTrue(incs[7].endswith("/sub1"), msg)
  
  
  class RewriterTests(unittest.TestCase):
  
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.testroot = tempfile.mkdtemp()
          self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')]
          self.tmpdir = tempfile.mkdtemp()
          self.workdir = os.path.join(self.tmpdir, 'foo')
          self.test_dir = os.path.join(src_root, 'test cases/rewrite')
  
      def tearDown(self):
          shutil.rmtree(self.tmpdir)
  
      def read_contents(self, fname):
          with open(os.path.join(self.workdir, fname)) as f:
              return f.read()
  
      def check_effectively_same(self, mainfile, truth):
          mf = self.read_contents(mainfile)
          t = self.read_contents(truth)
          # Rewriting is not guaranteed to do a perfect job of
          # maintaining whitespace.
          self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
  
      def prime(self, dirname):
          shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
  
      def test_basic(self):
          self.prime('1 basic')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'removed.txt')
          subprocess.check_output(self.rewrite_command + ['add',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'added.txt')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=trivialprog',
                                                          '--filename=notthere.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('meson.build', 'removed.txt')
  
      def test_subdir(self):
          self.prime('2 subdirs')
          top = self.read_contents('meson.build')
          s2 = self.read_contents('sub2/meson.build')
          subprocess.check_output(self.rewrite_command + ['remove',
                                                          '--target=something',
                                                          '--filename=second.c',
                                                          '--sourcedir', self.workdir])
          self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
          self.assertEqual(top, self.read_contents('meson.build'))
          self.assertEqual(s2, self.read_contents('sub2/meson.build'))
  
  
  if __name__ == '__main__':
      unittest.main()
  
files: run_unittests.py