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
  
  [... the rest is omitted ...]
files: mesonbuild/backend/backends.py
files: # Copyright 2012-2016 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  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
  
  [... the rest is omitted ...]
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_s
  
  [... the rest is omitted ...]
files: mesonbuild/build.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  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_s
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
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
  
  [... the rest is omitted ...]
files: run_unittests.py