files: # Copyright 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.
  
  # Public symbols for compilers sub-package when using 'from . import compilers'
  __all__ = [
      'CompilerType',
  
      'all_languages',
      'base_options',
      'clib_langs',
      'clink_langs',
      'c_suffixes',
      'cpp_suffixes',
      'get_macos_dylib_install_name',
      'get_base_compile_args',
      'get_base_link_args',
      'is_assembly',
      'is_header',
      'is_library',
      'is_llvm_ir',
      'is_object',
      'is_source',
      'lang_suffixes',
      'sanitizer_compile_args',
      'sort_clink',
  
      'ArmCCompiler',
      'ArmCPPCompiler',
      'ArmclangCCompiler',
      'ArmclangCPPCompiler',
      'CCompiler',
      'ClangCCompiler',
      'ClangCompiler',
      'ClangCPPCompiler',
      'ClangObjCCompiler',
      'ClangObjCPPCompiler',
      'CompilerArgs',
      'CPPCompiler',
      'DCompiler',
      'DmdDCompiler',
      'FortranCompiler',
      'G95FortranCompiler',
      'GnuCCompiler',
      'ElbrusCCompiler',
      'GnuCompiler',
      'GnuCPPCompiler',
      'ElbrusCPPCompiler',
      'GnuDCompiler',
      'GnuFortranCompiler',
      'ElbrusFortranCompiler',
      'GnuObjCCompiler',
      'GnuObjCPPCompiler',
      'IntelCompiler',
      'IntelCCompiler',
      'IntelCPPCompiler',
      'IntelFortranCompiler',
      'JavaCompiler',
      'LLVMDCompiler',
      'MonoCompiler',
      'VisualStudioCsCompiler',
      'NAGFortranCompiler',
      'ObjCCompiler',
      'ObjCPPCompiler',
      'Open64FortranCompiler',
      'PathScaleFortranCompiler',
      'PGIFortranCompiler',
      'RustCompiler',
      'SunFortranCompiler',
      'SwiftCompiler',
      'ValaCompiler',
      'VisualStudioCCompiler',
      'VisualStudioCPPCompiler',
  ]
  
  # Bring symbols from each module into compilers sub-package namespace
  from .compilers import (
      CompilerType,
      all_languages,
      base_options,
      clib_langs,
      clink_langs,
      c_suffixes,
      cpp_suffixes,
      get_macos_dylib_install_name,
      get_base_compile_args,
      get_base_link_args,
      is_header,
      is_source,
      is_assembly,
      is_llvm_ir,
      is_object,
      is_library,
      lang_suffixes,
      sanitizer_compile_args,
      sort_clink,
      ClangCompiler,
      CompilerArgs,
      GnuCompiler,
      IntelCompiler,
  )
  from .c import (
      CCompiler,
      ArmCCompiler,
      ArmclangCCompiler,
      ClangCCompiler,
      GnuCCompiler,
      ElbrusCCompiler,
      IntelCCompiler,
      VisualStudioCCompiler,
  )
  from .cpp import (
      CPPCompiler,
      ArmCPPCompiler,
      ArmclangCPPCompiler,
      ClangCPPCompiler,
      GnuCPPCompiler,
      ElbrusCPPCompiler,
      IntelCPPCompiler,
      VisualStudioCPPCompiler,
  )
  from .cs import MonoCompiler, VisualStudioCsCompiler
  from .d import (
      DCompiler,
      DmdDCompiler,
      GnuDCompiler,
      LLVMDCompiler,
  )
  from .fortran import (
      FortranCompiler,
      G95FortranCompiler,
      GnuFortranCompiler,
      ElbrusFortranCompiler,
      IntelFortranCompiler,
      NAGFortranCompiler,
      Open64FortranCompiler,
      PathScaleFortranCompiler,
      PGIFortranCompiler,
      SunFortranCompiler,
  )
  from .java import JavaCompiler
  from .objc import (
      ObjCCompiler,
      ClangObjCCompiler,
      GnuObjCCompiler,
  )
  from .objcpp import (
      ObjCPPCompiler,
      ClangObjCPPCompiler,
      GnuObjCPPCompiler,
  )
  from .rust import RustCompiler
  from .swift import SwiftCompiler
  from .vala import ValaCompiler
  
files: mesonbuild/compilers/__init__.py
files: # Copyright 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.
  
  # Public symbols for compilers sub-package when using 'from . import compilers'
  __all__ = [
      'CLANG_OSX',
      'CLANG_STANDARD',
      'CLANG_WIN',
      'GCC_CYGWIN',
      'GCC_MINGW',
      'GCC_OSX',
      'GCC_STANDARD',
      'ICC_OSX',
      'ICC_STANDARD',
      'ICC_WIN',
  
      'all_languages',
      'base_options',
      'clib_langs',
      'clink_langs',
      'c_suffixes',
      'cpp_suffixes',
      'get_macos_dylib_install_name',
      'get_base_compile_args',
      'get_base_link_args',
      'is_assembly',
      'is_header',
      'is_library',
      'is_llvm_ir',
      'is_object',
      'is_source',
      'lang_suffixes',
      'sanitizer_compile_args',
      'sort_clink',
  
      'ArmCCompiler',
      'ArmCPPCompiler',
      'ArmclangCCompiler',
      'ArmclangCPPCompiler',
      'CCompiler',
      'ClangCCompiler',
      'ClangCompiler',
      'ClangCPPCompiler',
      'ClangObjCCompiler',
      'ClangObjCPPCompiler',
      'CompilerArgs',
      'CPPCompiler',
      'DCompiler',
      'DmdDCompiler',
      'FortranCompiler',
      'G95FortranCompiler',
      'GnuCCompiler',
      'ElbrusCCompiler',
      'GnuCompiler',
      'GnuCPPCompiler',
      'ElbrusCPPCompiler',
      'GnuDCompiler',
      'GnuFortranCompiler',
      'ElbrusFortranCompiler',
      'GnuObjCCompiler',
      'GnuObjCPPCompiler',
      'IntelCompiler',
      'IntelCCompiler',
      'IntelCPPCompiler',
      'IntelFortranCompiler',
      'JavaCompiler',
      'LLVMDCompiler',
      'MonoCompiler',
      'VisualStudioCsCompiler',
      'NAGFortranCompiler',
      'ObjCCompiler',
      'ObjCPPCompiler',
      'Open64FortranCompiler',
      'PathScaleFortranCompiler',
      'PGIFortranCompiler',
      'RustCompiler',
      'SunFortranCompiler',
      'SwiftCompiler',
      'ValaCompiler',
      'VisualStudioCCompiler',
      'VisualStudioCPPCompiler',
  ]
  
  # Bring symbols from each module into compilers sub-package namespace
  from .compilers import (
      GCC_OSX,
      GCC_MINGW,
      GCC_CYGWIN,
      GCC_STANDARD,
      CLANG_OSX,
      CLANG_WIN,
      CLANG_STANDARD,
      ICC_OSX,
      ICC_WIN,
      ICC_STANDARD,
      all_languages,
      base_options,
      clib_langs,
      clink_langs,
      c_suffixes,
      cpp_suffixes,
      get_macos_dylib_install_name,
      get_base_compile_args,
      get_base_link_args,
      is_header,
      is_source,
      is_assembly,
      is_llvm_ir,
      is_object,
      is_library,
      lang_suffixes,
      sanitizer_compile_args,
      sort_clink,
      ClangCompiler,
      CompilerArgs,
      GnuCompiler,
      IntelCompiler,
  )
  from .c import (
      CCompiler,
      ArmCCompiler,
      ArmclangCCompiler,
      ClangCCompiler,
      GnuCCompiler,
      ElbrusCCompiler,
      IntelCCompiler,
      VisualStudioCCompiler,
  )
  from .cpp import (
      CPPCompiler,
      ArmCPPCompiler,
      ArmclangCPPCompiler,
      ClangCPPCompiler,
      GnuCPPCompiler,
      ElbrusCPPCompiler,
      IntelCPPCompiler,
      VisualStudioCPPCompiler,
  )
  from .cs import MonoCompiler, VisualStudioCsCompiler
  from .d import (
      DCompiler,
      DmdDCompiler,
      GnuDCompiler,
      LLVMDCompiler,
  )
  from .fortran import (
      FortranCompiler,
      G95FortranCompiler,
      GnuFortranCompiler,
      ElbrusFortranCompiler,
      IntelFortranCompiler,
      NAGFortranCompiler,
      Open64FortranCompiler,
      PathScaleFortranCompiler,
      PGIFortranCompiler,
      SunFortranCompiler,
  )
  from .java import JavaCompiler
  from .objc import (
      ObjCCompiler,
      ClangObjCCompiler,
      GnuObjCCompiler,
  )
  from .objcpp import (
      ObjCPPCompiler,
      ClangObjCPPCompiler,
      GnuObjCPPCompiler,
  )
  from .rust import RustCompiler
  from .swift import SwiftCompiler
  from .vala import ValaCompiler
  
files: mesonbuild/compilers/__init__.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import re
  import glob
  import os.path
  import subprocess
  import functools
  import itertools
  from pathlib import Path
  
  from .. import mlog
  from .. import coredata
  from . import compilers
  from ..mesonlib import (
      EnvironmentException, MesonException, version_compare, Popen_safe, listify,
      for_windows, for_darwin, for_cygwin, for_haiku, for_openbsd,
  )
  from .c_function_attributes import C_FUNC_ATTRIBUTES
  
  from .compilers import (
      CompilerType,
      get_largefile_args,
      gnu_winlibs,
      msvc_winlibs,
      vs32_instruction_set_args,
      vs64_instruction_set_args,
      ArmCompiler,
      ArmclangCompiler,
      ClangCompiler,
      Compiler,
      CompilerArgs,
      CrossNoRunException,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
      RunResult,
  )
  
  gnu_compiler_internal_libs = ('m', 'c', 'pthread', 'dl', 'rt')
  
  
  class CCompiler(Compiler):
      # TODO: Replace this manual cache with functools.lru_cache
      library_dirs_cache = {}
      program_dirs_cache = {}
      find_library_cache = {}
      internal_libs = gnu_compiler_internal_libs
  
      @staticmethod
      def attribute_check_func(name):
          try:
              return C_FUNC_ATTRIBUTES[name]
          except KeyError:
              raise MesonException('Unknown function attribute "{}"'.format(name))
  
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          # If a child ObjC or CPP class has already set it, don't set it ourselves
          if not hasattr(self, 'language'):
              self.language = 'c'
          super().__init__(exelist, version, **kwargs)
          self.id = 'unknown'
          self.is_cross = is_cross
          self.can_compile_suffixes.add('h')
          # If the exe wrapper was not found, pretend it wasn't set so that the
          # sanity check is skipped and compiler checks use fallbacks.
          if not exe_wrapper or not exe_wrapper.found():
              self.exe_wrapper = None
          else:
              self.exe_wrapper = exe_wrapper.get_command()
  
          # Set to None until we actually need to check this
          self.has_fatal_warnings_link_arg = None
  
      def needs_static_linker(self):
          return True # When compiling static libraries, so yes.
  
      def get_always_args(self):
          '''
          Args that are always-on for all C compilers other than MSVC
          '''
          return ['-pipe'] + get_largefile_args(self)
  
      def get_linker_debug_crt_args(self):
          """
          Arguments needed to select a debug crt for the linker
          This is only needed for MSVC
          """
          return []
  
      def get_no_stdinc_args(self):
          return ['-nostdinc']
  
      def get_no_stdlib_link_args(self):
          return ['-nostdlib']
  
      def get_warn_args(self, level):
          return self.warn_args[level]
  
      def get_no_warn_args(self):
          # Almost every compiler uses this for disabling warnings
          return ['-w']
  
      def get_soname_args(self, *args):
          return []
  
      def split_shlib_to_parts(self, fname):
          return None, fname
  
      # The default behavior is this, override in MSVC
      @functools.lru_cache(maxsize=None)
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if self.id == 'clang' and self.compiler_type == CompilerType.CLANG_OSX:
              return self.build_osx_rpath_args(build_dir, rpath_paths, build_rpath)
          return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return ['-MD', '-MQ', outtarget, '-MF', outfile]
  
      def depfile_for_object(self, objfile):
          return objfile + '.' + self.get_depfile_suffix()
  
      def get_depfile_suffix(self):
          return 'd'
  
      def get_exelist(self):
          return self.exelist[:]
  
      def get_linker_exelist(self):
          return self.exelist[:]
  
      def get_preprocess_only_args(self):
          return ['-E', '-P']
  
      def get_compile_only_args(self):
          return ['-c']
  
      def get_no_optimization_args(self):
          return ['-O0']
  
      def get_compiler_check_args(self):
          '''
          Get arguments useful for compiler checks such as being permissive in
          the code quality and not doing any optimization.
          '''
          return self.get_no_optimization_args()
  
      def get_allow_undefined_link_args(self):
          '''
          Get args for allowing undefined symbols when linking to a shared library
          '''
          if self.id in ('clang', 'gcc'):
              if self.compiler_type.is_osx_compiler:
                  # Apple ld
                  return ['-Wl,-undefined,dynamic_lookup']
              else:
                  # GNU ld and LLVM lld
                  return ['-Wl,--allow-shlib-undefined']
          elif self.id == 'msvc':
              # link.exe
              return ['/FORCE:UNRESOLVED']
          # FIXME: implement other linkers
          return []
  
      def get_output_args(self, target):
          return ['-o', target]
  
      def get_linker_output_args(self, outputname):
          return ['-o', outputname]
  
      def get_coverage_args(self):
          return ['--coverage']
  
      def get_coverage_link_args(self):
          return ['--coverage']
  
      def get_werror_args(self):
          return ['-Werror']
  
      def get_std_exe_link_args(self):
          return []
  
      def get_include_args(self, path, is_system):
          if path == '':
              path = '.'
          if is_system:
              return ['-isystem', path]
          return ['-I' + path]
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      @functools.lru_cache()
      def _get_search_dirs(self, env):
          extra_args = ['--print-search-dirs']
          stdo = None
          with self._build_wrapper('', env, extra_args, None, 'compile', True) as p:
              stdo = p.stdo
          return stdo
  
      @staticmethod
      def _split_fetch_real_dirs(pathstr, sep=':'):
          paths = []
          for p in pathstr.split(sep):
              p = Path(p)
              if p.exists():
                  paths.append(p.resolve().as_posix())
          return tuple(paths)
  
      def get_compiler_dirs(self, env, name):
          '''
          Get dirs from the compiler, either `libraries:` or `programs:`
          '''
          stdo = self._get_search_dirs(env)
          for line in stdo.split('\n'):
              if line.startswith(name + ':'):
                  return CCompiler._split_fetch_real_dirs(line.split('=', 1)[1])
          return ()
  
      @functools.lru_cache()
      def get_library_dirs(self, env):
          return self.get_compiler_dirs(env, 'libraries')
  
      @functools.lru_cache()
      def get_program_dirs(self, env):
          '''
          Programs used by the compiler. Also where toolchain DLLs such as
          libstdc++-6.dll are found with MinGW.
          '''
          return self.get_compiler_dirs(env, 'programs')
  
      def get_pic_args(self):
          return ['-fPIC']
  
      def name_string(self):
          return ' '.join(self.exelist)
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-include', os.path.basename(header)]
  
      def get_pch_name(self, header_name):
          return os.path.basename(header_name) + '.' + self.get_pch_suffix()
  
      def get_linker_search_args(self, dirname):
          return ['-L' + dirname]
  
      def get_default_include_dirs(self):
          return []
  
      def gen_export_dynamic_link_args(self, env):
          if for_windows(env.is_cross_build(), env) or for_cygwin(env.is_cross_build(), env):
              return ['-Wl,--export-all-symbols']
          elif for_darwin(env.is_cross_build(), env):
              return []
          else:
              return ['-Wl,-export-dynamic']
  
      def gen_import_library_args(self, implibname):
          """
          The name of the outputted import library
  
          This implementation is used only on Windows by compilers that use GNU ld
          """
          return ['-Wl,--out-implib=' + implibname]
  
      def sanity_check_impl(self, work_dir, environment, sname, code):
          mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist))
          mlog.debug('Is cross compiler: %s.' % str(self.is_cross))
  
          extra_flags = []
          source_name = os.path.join(work_dir, sname)
          binname = sname.rsplit('.', 1)[0]
          if self.is_cross:
              binname += '_cross'
              if self.exe_wrapper is None:
                  # Linking cross built apps is painful. You can't really
                  # tell if you should use -nostdlib or not and for example
                  # on OSX the compiler binary is the same but you need
                  # a ton of compiler flags to differentiate between
                  # arm and x86_64. So just compile.
                  extra_flags += self.get_cross_extra_flags(environment, link=False)
                  extra_flags += self.get_compile_only_args()
              else:
                  extra_flags += self.get_cross_extra_flags(environment, link=True)
          # Is a valid executable output for all toolchains and platforms
          binname += '.exe'
          # Write binary check source
          binary_name = os.path.join(work_dir, binname)
          with open(source_name, 'w') as ofile:
              ofile.write(code)
          # Compile sanity check
          cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name)
          pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir)
          mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist))
          mlog.debug('Sanity check compile stdout:')
          mlog.debug(stdo)
          mlog.debug('-----\nSanity check compile stderr:')
          mlog.debug(stde)
          mlog.debug('-----')
          if pc.returncode != 0:
              raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string()))
          # Run sanity check
          if self.is_cross:
              if self.exe_wrapper is None:
                  # Can't check if the binaries run so we have to assume they do
                  return
              cmdlist = self.exe_wrapper + [binary_name]
          else:
              cmdlist = [binary_name]
          mlog.debug('Running test binary command: ' + ' '.join(cmdlist))
          try:
              pe = subprocess.Popen(cmdlist)
          except Exception as e:
              raise EnvironmentException('Could not invoke sanity test executable: %s.' % str(e))
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string()))
  
      def sanity_check(self, work_dir, environment):
          code = 'int main(int argc, char **argv) { int class=0; return class; }\n'
          return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code)
  
      def check_header(self, hname, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname}
          code = '''{prefix}
          #include <{header}>'''
          return self.compiles(code.format(**fargs), env, extra_args, dependencies)
  
      def has_header(self, hname, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname}
          code = '''{prefix}
          #ifdef __has_include
           #if !__has_include("{header}")
            #error "Header '{header}' could not be found"
           #endif
          #else
           #include <{header}>
          #endif'''
          return self.compiles(code.format(**fargs), env, extra_args,
                               dependencies, 'preprocess')
  
      def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol}
          t = '''{prefix}
          #include <{header}>
          int main () {{
              /* If it's not defined as a macro, try to use as a symbol */
              #ifndef {symbol}
                  {symbol};
              #endif
          }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          if extra_args is None:
              extra_args = []
          elif isinstance(extra_args, str):
              extra_args = [extra_args]
          if dependencies is None:
              dependencies = []
          elif not isinstance(dependencies, list):
              dependencies = [dependencies]
          # Collect compiler arguments
          args = CompilerArgs(self)
          for d in dependencies:
              # Add compile flags needed by dependencies
              args += d.get_compile_args()
              if d.need_threads():
                  args += self.thread_flags(env)
              elif d.need_openmp():
                  args += self.openmp_flags()
              if mode == 'link':
                  # Add link flags needed to find dependencies
                  args += d.get_link_args()
                  if d.need_threads():
                      args += self.thread_link_flags(env)
          # Select a CRT if needed since we're linking
          if mode == 'link':
              args += self.get_linker_debug_crt_args()
          # Read c_args/cpp_args/etc from the cross-info file (if needed)
          args += self.get_cross_extra_flags(env, link=(mode == 'link'))
          if not self.is_cross:
              if mode == 'preprocess':
                  # Add CPPFLAGS from the env.
                  args += env.coredata.get_external_preprocess_args(self.language)
              elif mode == 'compile':
                  # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env
                  args += env.coredata.get_external_args(self.language)
              elif mode == 'link':
                  # Add LDFLAGS from the env
                  args += env.coredata.get_external_link_args(self.language)
          args += self.get_compiler_check_args()
          # extra_args must override all other arguments, so we add them last
          args += extra_args
          return args
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          with self._build_wrapper(code, env, extra_args, dependencies, mode) as p:
              return p.returncode == 0
  
      def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False):
          args = self._get_compiler_check_args(env, extra_args, dependencies, mode)
          return self.compile(code, args, mode, want_output=want_output)
  
      def links(self, code, env, extra_args=None, dependencies=None):
          return self.compiles(code, env, extra_args, dependencies, mode='link')
  
      def run(self, code, env, extra_args=None, dependencies=None):
          if self.is_cross and self.exe_wrapper is None:
              raise CrossNoRunException('Can not run test applications in this cross environment.')
          with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p:
              if p.returncode != 0:
                  mlog.debug('Could not compile test file %s: %d\n' % (
                      p.input_name,
                      p.returncode))
                  return RunResult(False)
              if self.is_cross:
                  cmdlist = self.exe_wrapper + [p.output_name]
              else:
                  cmdlist = p.output_name
              try:
                  pe, so, se = Popen_safe(cmdlist)
              except Exception as e:
                  mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e))
                  return RunResult(False)
  
          mlog.debug('Program stdout:\n')
          mlog.debug(so)
          mlog.debug('Program stderr:\n')
          mlog.debug(se)
          return RunResult(True, pe.returncode, so, se)
  
      def _compile_int(self, expression, prefix, env, extra_args, dependencies):
          fargs = {'prefix': prefix, 'expression': expression}
          t = '''#include <stdio.h>
          {prefix}
          int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def cross_compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies):
          # Try user's guess first
          if isinstance(guess, int):
              if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies):
                  return guess
  
          # If no bounds are given, compute them in the limit of int32
          maxint = 0x7fffffff
          minint = -0x80000000
          if not isinstance(low, int) or not isinstance(high, int):
              if self._compile_int('%s >= 0' % (expression), prefix, env, extra_args, dependencies):
                  low = cur = 0
                  while self._compile_int('%s > %d' % (expression, cur), prefix, env, extra_args, dependencies):
                      low = cur + 1
                      if low > maxint:
                          raise EnvironmentException('Cross-compile check overflowed')
                      cur = cur * 2 + 1
                      if cur > maxint:
                          cur = maxint
                  high = cur
              else:
                  low = cur = -1
                  while self._compile_int('%s < %d' % (expression, cur), prefix, env, extra_args, dependencies):
                      high = cur - 1
                      if high < minint:
                          raise EnvironmentException('Cross-compile check overflowed')
                      cur = cur * 2
                      if cur < minint:
                          cur = minint
                  low = cur
          else:
              # Sanity check limits given by user
              if high < low:
                  raise EnvironmentException('high limit smaller than low limit')
              condition = '%s <= %d && %s >= %d' % (expression, high, expression, low)
              if not self._compile_int(condition, prefix, env, extra_args, dependencies):
                  raise EnvironmentException('Value out of given range')
  
          # Binary search
          while low != high:
              cur = low + int((high - low) / 2)
              if self._compile_int('%s <= %d' % (expression, cur), prefix, env, extra_args, dependencies):
                  high = cur
              else:
                  low = cur + 1
  
          return low
  
      def compute_int(self, expression, low, high, guess, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          if self.is_cross:
              return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies)
          fargs = {'prefix': prefix, 'expression': expression}
          t = '''#include<stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              printf("%ld\\n", (long)({expression}));
              return 0;
          }};'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              return -1
          if res.returncode != 0:
              raise EnvironmentException('Could not run compute_int test binary.')
          return int(res.stdout)
  
      def cross_sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              {type} something;
          }}'''
          if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
              return -1
          return self.cross_compute_int('sizeof(%s)' % typename, None, None, None, prefix, env, extra_args, dependencies)
  
      def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          if self.is_cross:
              return self.cross_sizeof(typename, prefix, env, extra_args, dependencies)
          t = '''#include<stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              printf("%ld\\n", (long)(sizeof({type})));
              return 0;
          }};'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              return -1
          if res.returncode != 0:
              raise EnvironmentException('Could not run sizeof test binary.')
          return int(res.stdout)
  
      def cross_alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              {type} something;
          }}'''
          if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
              return -1
          t = '''#include <stddef.h>
          {prefix}
          struct tmp {{
              char c;
              {type} target;
          }};'''
          return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t.format(**fargs), env, extra_args, dependencies)
  
      def alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          if self.is_cross:
              return self.cross_alignment(typename, prefix, env, extra_args, dependencies)
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          #include <stddef.h>
          {prefix}
          struct tmp {{
              char c;
              {type} target;
          }};
          int main(int argc, char **argv) {{
              printf("%d", (int)offsetof(struct tmp, target));
              return 0;
          }}'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              raise EnvironmentException('Could not compile alignment test.')
          if res.returncode != 0:
              raise EnvironmentException('Could not run alignment test binary.')
          align = int(res.stdout)
          if align == 0:
              raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename)
          return align
  
      def get_define(self, dname, prefix, env, extra_args, dependencies):
          delim = '"MESON_GET_DEFINE_DELIMITER"'
          fargs = {'prefix': prefix, 'define': dname, 'delim': delim}
          code = '''
          {prefix}
          #ifndef {define}
          # define {define}
          #endif
          {delim}\n{define}'''
          args = self._get_compiler_check_args(env, extra_args, dependencies,
                                               mode='preprocess').to_native()
          with self.compile(code.format(**fargs), args, 'preprocess') as p:
              if p.returncode != 0:
                  raise EnvironmentException('Could not get define {!r}'.format(dname))
          # Get the preprocessed value after the delimiter,
          # minus the extra newline at the end and
          # merge string literals.
          return CCompiler.concatenate_string_literals(p.stdo.split(delim + '\n')[-1][:-1])
  
      def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies):
          if rtype == 'string':
              fmt = '%s'
              cast = '(char*)'
          elif rtype == 'int':
              fmt = '%lli'
              cast = '(long long int)'
          else:
              raise AssertionError('BUG: Unknown return type {!r}'.format(rtype))
          fargs = {'prefix': prefix, 'f': fname, 'cast': cast, 'fmt': fmt}
          code = '''{prefix}
          #include <stdio.h>
          int main(int argc, char *argv[]) {{
              printf ("{fmt}", {cast} {f}());
          }}'''.format(**fargs)
          res = self.run(code, env, extra_args, dependencies)
          if not res.compiled:
              m = 'Could not get return value of {}()'
              raise EnvironmentException(m.format(fname))
          if rtype == 'string':
              return res.stdout
          elif rtype == 'int':
              try:
                  return int(res.stdout.strip())
              except ValueError:
                  m = 'Return value of {}() is not an int'
                  raise EnvironmentException(m.format(fname))
  
      @staticmethod
      def _no_prototype_templ():
          """
          Try to find the function without a prototype from a header by defining
          our own dummy prototype and trying to link with the C library (and
          whatever else the compiler links in by default). This is very similar
          to the check performed by Autoconf for AC_CHECK_FUNCS.
          """
          # Define the symbol to something else since it is defined by the
          # includes or defines listed by the user or by the compiler. This may
          # include, for instance _GNU_SOURCE which must be defined before
          # limits.h, which includes features.h
          # Then, undef the symbol to get rid of it completely.
          head = '''
          #define {func} meson_disable_define_of_{func}
          {prefix}
          #include <limits.h>
          #undef {func}
          '''
          # Override any GCC internal prototype and declare our own definition for
          # the symbol. Use char because that's unlikely to be an actual return
          # value for a function which ensures that we override the definition.
          head += '''
          #ifdef __cplusplus
          extern "C"
          #endif
          char {func} ();
          '''
          # The actual function call
          main = '''
          int main () {{
            return {func} ();
          }}'''
          return head, main
  
      @staticmethod
      def _have_prototype_templ():
          """
          Returns a head-er and main() call that uses the headers listed by the
          user for the function prototype while checking if a function exists.
          """
          # Add the 'prefix', aka defines, includes, etc that the user provides
          # This may include, for instance _GNU_SOURCE which must be defined
          # before limits.h, which includes features.h
          head = '{prefix}\n#include <limits.h>\n'
          # We don't know what the function takes or returns, so return it as an int.
          # Just taking the address or comparing it to void is not enough because
          # compilers are smart enough to optimize it away. The resulting binary
          # is not run so we don't care what the return value is.
          main = '''\nint main() {{
              void *a = (void*) &{func};
              long b = (long) a;
              return (int) b;
          }}'''
          return head, main
  
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          """
          First, this function looks for the symbol in the default libraries
          provided by the compiler (stdlib + a few others usually). If that
          fails, it checks if any of the headers specified in the prefix provide
          an implementation of the function, and if that fails, it checks if it's
          implemented as a compiler-builtin.
          """
          if extra_args is None:
              extra_args = []
  
          # Short-circuit if the check is already provided by the cross-info file
          varname = 'has function ' + funcname
          varname = varname.replace(' ', '_')
          if self.is_cross:
              val = env.cross_info.config['properties'].get(varname, None)
              if val is not None:
                  if isinstance(val, bool):
                      return val
                  raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname))
  
          fargs = {'prefix': prefix, 'func': funcname}
  
          # glibc defines functions that are not available on Linux as stubs that
          # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail
          # instead of detecting the stub as a valid symbol.
          # We already included limits.h earlier to ensure that these are defined
          # for stub functions.
          stubs_fail = '''
          #if defined __stub_{func} || defined __stub___{func}
          fail fail fail this function is not going to work
          #endif
          '''
  
          # If we have any includes in the prefix supplied by the user, assume
          # that the user wants us to use the symbol prototype defined in those
          # includes. If not, then try to do the Autoconf-style check with
          # a dummy prototype definition of our own.
          # This is needed when the linker determines symbol availability from an
          # SDK based on the prototype in the header provided by the SDK.
          # Ignoring this prototype would result in the symbol always being
          # marked as available.
          if '#include' in prefix:
              head, main = self._have_prototype_templ()
          else:
              head, main = self._no_prototype_templ()
          templ = head + stubs_fail + main
  
          if self.links(templ.format(**fargs), env, extra_args, dependencies):
              return True
  
          # MSVC does not have compiler __builtin_-s.
          if self.get_id() == 'msvc':
              return False
  
          # Detect function as a built-in
          #
          # Some functions like alloca() are defined as compiler built-ins which
          # are inlined by the compiler and you can't take their address, so we
          # need to look for them differently. On nice compilers like clang, we
          # can just directly use the __has_builtin() macro.
          fargs['no_includes'] = '#include' not in prefix
          t = '''{prefix}
          int main() {{
          #ifdef __has_builtin
              #if !__has_builtin(__builtin_{func})
                  #error "__builtin_{func} not found"
              #endif
          #elif ! defined({func})
              /* Check for __builtin_{func} only if no includes were added to the
               * prefix above, which means no definition of {func} can be found.
               * We would always check for this, but we get false positives on
               * MSYS2 if we do. Their toolchain is broken, but we can at least
               * give them a workaround. */
              #if {no_includes:d}
                  __builtin_{func};
              #else
                  #error "No definition for __builtin_{func} found in the prefix"
              #endif
          #endif
          }}'''
          return self.links(t.format(**fargs), env, extra_args, dependencies)
  
      def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'}
          # Create code that accesses all members
          members = ''
          for member in membernames:
              members += '{}.{};\n'.format(fargs['name'], member)
          fargs['members'] = members
          t = '''{prefix}
          void bar() {{
              {type} {name};
              {members}
          }};'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def has_type(self, typename, prefix, env, extra_args, dependencies=None):
          fargs = {'prefix': prefix, 'type': typename}
          t = '''{prefix}
          void bar() {{
              sizeof({type});
          }};'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def symbols_have_underscore_prefix(self, env):
          '''
          Check if the compiler prefixes an underscore to global C symbols
          '''
          symbol_name = b'meson_uscore_prefix'
          code = '''#ifdef __cplusplus
          extern "C" {
          #endif
          void ''' + symbol_name.decode() + ''' () {}
          #ifdef __cplusplus
          }
          #endif
          '''
          args = self.get_cross_extra_flags(env, link=False)
          args += self.get_compiler_check_args()
          n = 'symbols_have_underscore_prefix'
          with self.compile(code, args, 'compile', want_output=True) as p:
              if p.returncode != 0:
                  m = 'BUG: Unable to compile {!r} check: {}'
                  raise RuntimeError(m.format(n, p.stdo))
              if not os.path.isfile(p.output_name):
                  m = 'BUG: Can\'t find compiled test code for {!r} check'
                  raise RuntimeError(m.format(n))
              with open(p.output_name, 'rb') as o:
                  for line in o:
                      # Check if the underscore form of the symbol is somewhere
                      # in the output file.
                      if b'_' + symbol_name in line:
                          return True
                      # Else, check if the non-underscored form is present
                      elif symbol_name in line:
                          return False
          raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n))
  
      def _get_patterns(self, env, prefixes, suffixes, shared=False):
          patterns = []
          for p in prefixes:
              for s in suffixes:
                  patterns.append(p + '{}.' + s)
          if shared and for_openbsd(self.is_cross, env):
              # Shared libraries on OpenBSD can be named libfoo.so.X.Y:
              # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs
              #
              # This globbing is probably the best matching we can do since regex
              # is expensive. It's wrong in many edge cases, but it will match
              # correctly-named libraries and hopefully no one on OpenBSD names
              # their files libfoo.so.9a.7b.1.0
              patterns.append('lib{}.so.[0-9]*.[0-9]*')
          return patterns
  
      def get_library_naming(self, env, libtype, strict=False):
          '''
          Get library prefixes and suffixes for the target platform ordered by
          priority
          '''
          stlibext = ['a']
          # We've always allowed libname to be both `foo` and `libfoo`,
          # and now people depend on it
          if strict and self.id != 'msvc': # lib prefix is not usually used with msvc
              prefixes = ['lib']
          else:
              prefixes = ['lib', '']
          # Library suffixes and prefixes
          if for_darwin(env.is_cross_build(), env):
              shlibext = ['dylib']
          elif for_windows(env.is_cross_build(), env):
              # FIXME: .lib files can be import or static so we should read the
              # file, figure out which one it is, and reject the wrong kind.
              if self.id == 'msvc':
                  shlibext = ['lib']
              else:
                  shlibext = ['dll.a', 'lib', 'dll']
              # Yep, static libraries can also be foo.lib
              stlibext += ['lib']
          elif for_cygwin(env.is_cross_build(), env):
              shlibext = ['dll', 'dll.a']
              prefixes = ['cyg'] + prefixes
          else:
              # Linux/BSDs
              shlibext = ['so']
          patterns = []
          # Search priority
          if libtype in ('default', 'shared-static'):
              patterns += self._get_patterns(env, prefixes, shlibext, True)
              patterns += self._get_patterns(env, prefixes, stlibext, False)
          elif libtype == 'static-shared':
              patterns += self._get_patterns(env, prefixes, stlibext, False)
              patterns += self._get_patterns(env, prefixes, shlibext, True)
          elif libtype == 'shared':
              patterns += self._get_patterns(env, prefixes, shlibext, True)
          elif libtype == 'static':
              patterns += self._get_patterns(env, prefixes, stlibext, False)
          else:
              raise AssertionError('BUG: unknown libtype {!r}'.format(libtype))
          return tuple(patterns)
  
      @staticmethod
      def _sort_shlibs_openbsd(libs):
          filtered = []
          for lib in libs:
              # Validate file as a shared library of type libfoo.so.X.Y
              ret = lib.rsplit('.so.', maxsplit=1)
              if len(ret) != 2:
                  continue
              try:
                  float(ret[1])
              except ValueError:
                  continue
              filtered.append(lib)
          float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1])
          return sorted(filtered, key=float_cmp, reverse=True)
  
      @classmethod
      def _get_trials_from_pattern(cls, pattern, directory, libname):
          f = Path(directory) / pattern.format(libname)
          # Globbing for OpenBSD
          if '*' in pattern:
              # NOTE: globbing matches directories and broken symlinks
              # so we have to do an isfile test on it later
              return cls._sort_shlibs_openbsd(glob.glob(str(f)))
          return [f.as_posix()]
  
      @staticmethod
      def _get_file_from_list(files):
          for f in files:
              if os.path.isfile(f):
                  return f
          return None
  
      def find_library_real(self, libname, env, extra_dirs, code, libtype):
          # First try if we can just add the library as -l.
          # Gcc + co seem to prefer builtin lib dirs to -L dirs.
          # Only try to find std libs if no extra dirs specified.
          if not extra_dirs or libname in self.internal_libs:
              args = ['-l' + libname]
              largs = self.linker_to_compiler_args(self.get_allow_undefined_link_args())
              if self.links(code, env, extra_args=(args + largs)):
                  return args
              # Don't do a manual search for internal libs
              if libname in self.internal_libs:
                  return None
          # Not found or we want to use a specific libtype? Try to find the
          # library file itself.
          patterns = self.get_library_naming(env, libtype)
          # Search in the specified dirs, and then in the system libraries
          for d in itertools.chain(extra_dirs, self.get_library_dirs(env)):
              for p in patterns:
                  trial = self._get_trials_from_pattern(p, d, libname)
                  if not trial:
                      continue
                  # We just check whether the library exists. We can't do a link
                  # check because the library might have unresolved symbols that
                  # require other libraries.
                  trial = self._get_file_from_list(trial)
                  if not trial:
                      continue
                  return [trial]
          return None
  
      def find_library_impl(self, libname, env, extra_dirs, code, libtype):
          # These libraries are either built-in or invalid
          if libname in self.ignore_libs:
              return []
          if isinstance(extra_dirs, str):
              extra_dirs = [extra_dirs]
          key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype)
          if key not in self.find_library_cache:
              value = self.find_library_real(libname, env, extra_dirs, code, libtype)
              self.find_library_cache[key] = value
          else:
              value = self.find_library_cache[key]
          if value is None:
              return None
          return value[:]
  
      def find_library(self, libname, env, extra_dirs, libtype='default'):
          code = 'int main(int argc, char **argv) { return 0; }'
          return self.find_library_impl(libname, env, extra_dirs, code, libtype)
  
      def thread_flags(self, env):
          if for_haiku(self.is_cross, env):
              return []
          return ['-pthread']
  
      def thread_link_flags(self, env):
          if for_haiku(self.is_cross, env):
              return []
          return ['-pthread']
  
      def linker_to_compiler_args(self, args):
          return args
  
      def has_arguments(self, args, env, code, mode):
          return self.compiles(code, env, extra_args=args, mode=mode)
  
      def has_multi_arguments(self, args, env):
          for arg in args[:]:
              # some compilers, e.g. GCC, don't warn for unsupported warning-disable
              # flags, so when we are testing a flag like "-Wno-forgotten-towel", also
              # check the equivalent enable flag too "-Wforgotten-towel"
              if arg.startswith('-Wno-'):
                      args.append('-W' + arg[5:])
              if arg.startswith('-Wl,'):
                  mlog.warning('{} looks like a linker argument, '
                               'but has_argument and other similar methods only '
                               'support checking compiler arguments. Using them '
                               'to check linker arguments are never supported, '
                               'and results are likely to be wrong regardless of '
                               'the compiler you are using. has_link_argument or '
                               'other similar method can be used instead.'
                               .format(arg))
          code = 'int i;\n'
          return self.has_arguments(args, env, code, mode='compile')
  
      def has_multi_link_arguments(self, args, env):
          # First time we check for link flags we need to first check if we have
          # --fatal-warnings, otherwise some linker checks could give some
          # false positive.
          fatal_warnings_args = ['-Wl,--fatal-warnings']
          if self.has_fatal_warnings_link_arg is None:
              self.has_fatal_warnings_link_arg = False
              self.has_fatal_warnings_link_arg = self.has_multi_link_arguments(fatal_warnings_args, env)
  
          if self.has_fatal_warnings_link_arg:
              args = fatal_warnings_args + args
  
          args = self.linker_to_compiler_args(args)
          code = 'int main(int argc, char **argv) { return 0; }'
          return self.has_arguments(args, env, code, mode='link')
  
      @staticmethod
      def concatenate_string_literals(s):
          pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)')
          ret = s
          m = pattern.match(ret)
          while m:
              ret = ''.join(m.group('pre', 'str1', 'str2', 'post'))
              m = pattern.match(ret)
          return ret
  
      def has_func_attribute(self, name, env):
          # Just assume that if we're not on windows that dllimport and dllexport
          # don't work
          if not (for_windows(env.is_cross_build(), env) or
                  for_cygwin(env.is_cross_build(), env)):
              if name in ['dllimport', 'dllexport']:
                  return False
  
          # Clang and GCC both return warnings if the __attribute__ is undefined,
          # so set -Werror
          return self.compiles(self.attribute_check_func(name), env, extra_args='-Werror')
  
  
  class ClangCCompiler(ClangCompiler, CCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ClangCompiler.__init__(self, compiler_type)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c99', 'c11',
                                                          'gnu89', 'gnu99', 'gnu11'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def get_linker_always_args(self):
          basic = super().get_linker_always_args()
          if self.compiler_type.is_osx_compiler:
              return basic + ['-Wl,-headerpad_max_install_names']
          return basic
  
  
  class ArmclangCCompiler(ArmclangCompiler, CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmclangCompiler.__init__(self)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c90', 'c99', 'c11',
                                                          'gnu90', 'gnu99', 'gnu11'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class GnuCCompiler(GnuCompiler, CCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          GnuCompiler.__init__(self, compiler_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c99', 'c11',
                                                          'gnu89', 'gnu99', 'gnu11'],
                                                         'none')})
          if self.compiler_type == CompilerType.GCC_MINGW:
              opts.update({
                  'c_winlibs': coredata.UserArrayOption('c_winlibs', 'Standard Win libraries to link against',
                                                        gnu_winlibs), })
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          if self.compiler_type == CompilerType.GCC_MINGW:
              return options['c_winlibs'].value[:]
          return []
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-fpch-preprocess', '-include', os.path.basename(header)]
  
  
  class ElbrusCCompiler(GnuCCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuCCompiler.__init__(self, exelist, version, compiler_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, compiler_type, defines)
  
      # It does support some various ISO standards and c/gnu 90, 9x, 1x in addition to those which GNU CC supports.
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11',
                                                          'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11',
                                                          'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999'],
                                                         'none')})
          return opts
  
      # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error.
      # So we should explicitly fail at this case.
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if funcname == 'lchmod':
              return False
          else:
              return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
  
  class IntelCCompiler(IntelCompiler, CCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          IntelCompiler.__init__(self, compiler_type)
          self.lang_header = 'c-header'
          default_warn_args = ['-Wall', '-w3', '-diag-disable:remark', '-Wpch-messages']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          c_stds = ['c89', 'c99']
          g_stds = ['gnu89', 'gnu99']
          if version_compare(self.version, '>=16.0.0'):
              c_stds += ['c11']
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none'] + c_stds + g_stds,
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def has_arguments(self, args, env, code, mode):
          return super().has_arguments(args + ['-diag-error', '10006'], env, code, mode)
  
  
  class VisualStudioCCompiler(CCompiler):
      std_warn_args = ['/W3']
      std_opt_args = ['/O2']
      ignore_libs = gnu_compiler_internal_libs
      internal_libs = ()
  
      crt_args = {'none': [],
                  'md': ['/MD'],
                  'mdd': ['/MDd'],
                  'mt': ['/MT'],
                  'mtd': ['/MTd'],
                  }
  
      def __init__(self, exelist, version, is_cross, exe_wrap, is_64):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
          self.id = 'msvc'
          # /showIncludes is needed for build dependency tracking in Ninja
          # See: https://ninja-build.org/manual.html#_deps
          self.always_args = ['/nologo', '/showIncludes']
          self.warn_args = {'1': ['/W2'],
                            '2': ['/W3'],
                            '3': ['/W4']}
          self.base_options = ['b_pch', 'b_ndebug', 'b_vscrt'] # FIXME add lto, pgo and the like
          self.is_64 = is_64
  
      # Override CCompiler.get_always_args
      def get_always_args(self):
          return self.always_args
  
      def get_linker_debug_crt_args(self):
          """
          Arguments needed to select a debug crt for the linker
  
          Sometimes we need to manually select the CRT (C runtime) to use with
          MSVC. One example is when trying to link with static libraries since
          MSVC won't auto-select a CRT for us in that case and will error out
          asking us to select one.
          """
          return ['/MDd']
  
      def get_buildtype_args(self, buildtype):
          args = compilers.msvc_buildtype_args[buildtype]
          if version_compare(self.version, '<18.0'):
              args = [arg for arg in args if arg != '/Gw']
          return args
  
      def get_buildtype_linker_args(self, buildtype):
          return compilers.msvc_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'pch'
  
      def get_pch_name(self, header):
          chopped = os.path.basename(header).split('.')[:-1]
          chopped.append(self.get_pch_suffix())
          pchname = '.'.join(chopped)
          return pchname
  
      def get_pch_use_args(self, pch_dir, header):
          base = os.path.basename(header)
          pchname = self.get_pch_name(header)
          return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)]
  
      def get_preprocess_only_args(self):
          return ['/EP']
  
      def get_compile_only_args(self):
          return ['/c']
  
      def get_no_optimization_args(self):
          return ['/Od']
  
      def get_output_args(self, target):
          if target.endswith('.exe'):
              return ['/Fe' + target]
          return ['/Fo' + target]
  
      def get_optimization_args(self, optimization_level):
          return compilers.msvc_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return compilers.msvc_debug_args[is_debug]
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      def get_linker_exelist(self):
          return ['link'] # FIXME, should have same path as compiler.
  
      def get_linker_always_args(self):
          return ['/nologo']
  
      def get_linker_output_args(self, outputname):
          return ['/OUT:' + outputname]
  
      def get_linker_search_args(self, dirname):
          return ['/LIBPATH:' + dirname]
  
      def linker_to_compiler_args(self, args):
          return ['/link'] + args
  
      def get_gui_app_args(self, value):
          # the default is for the linker to guess the subsystem based on presence
          # of main or WinMain symbols, so always be explicit
          if value:
              return ['/SUBSYSTEM:WINDOWS']
          else:
              return ['/SUBSYSTEM:CONSOLE']
  
      def get_pic_args(self):
          return [] # PIC is handled by the loader on Windows
  
      def gen_export_dynamic_link_args(self, env):
          return [] # Not applicable with MSVC
  
      def get_std_shared_lib_link_args(self):
          return ['/DLL']
  
      def gen_vs_module_defs_args(self, defsfile):
          if not isinstance(defsfile, str):
              raise RuntimeError('Module definitions file should be str')
          # With MSVC, DLLs only export symbols that are explicitly exported,
          # so if a module defs file is specified, we use that to export symbols
          return ['/DEF:' + defsfile]
  
      def gen_pch_args(self, header, source, pchname):
          objname = os.path.splitext(pchname)[0] + '.obj'
          return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname]
  
      def gen_import_library_args(self, implibname):
          "The name of the outputted import library"
          return ['/IMPLIB:' + implibname]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return []
  
      def openmp_flags(self):
          return ['/openmp']
  
      # FIXME, no idea what these should be.
      def thread_flags(self, env):
          return []
  
      def thread_link_flags(self, env):
          return []
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_winlibs': coredata.UserArrayOption('c_winlibs',
                                                             'Windows libs to link against.',
                                                             msvc_winlibs)})
          return opts
  
      def get_option_link_args(self, options):
          return options['c_winlibs'].value[:]
  
      @classmethod
      def unix_args_to_native(cls, args):
          result = []
          for i in args:
              # -mms-bitfields is specific to MinGW-GCC
              # -pthread is only valid for GCC
              if i in ('-mms-bitfields', '-pthread'):
                  continue
              if i.startswith('-L'):
                  i = '/LIBPATH:' + i[2:]
              # Translate GNU-style -lfoo library name to the import library
              elif i.startswith('-l'):
                  name = i[2:]
                  if name in cls.ignore_libs:
                      # With MSVC, these are provided by the C runtime which is
                      # linked in by default
                      continue
                  else:
                      i = name + '.lib'
              # -pthread in link flags is only used on Linux
              elif i == '-pthread':
                  continue
              result.append(i)
          return result
  
      def get_werror_args(self):
          return ['/WX']
  
      def get_include_args(self, path, is_system):
          if path == '':
              path = '.'
          # msvc does not have a concept of system header dirs.
          return ['-I' + path]
  
      # Visual Studio is special. It ignores some arguments it does not
      # understand and you can't tell it to error out on those.
      # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t
      def has_arguments(self, args, env, code, mode):
          warning_text = '4044' if mode == 'link' else '9002'
          with self._build_wrapper(code, env, extra_args=args, mode=mode) as p:
              if p.returncode != 0:
                  return False
              return not(warning_text in p.stde or warning_text in p.stdo)
  
      def get_compile_debugfile_args(self, rel_obj, pch=False):
          pdbarr = rel_obj.split('.')[:-1]
          pdbarr += ['pdb']
          args = ['/Fd' + '.'.join(pdbarr)]
          # When generating a PDB file with PCH, all compile commands write
          # to the same PDB file. Hence, we need to serialize the PDB
          # writes using /FS since we do parallel builds. This slows down the
          # build obviously, which is why we only do this when PCH is on.
          # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was
          # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx
          if pch and version_compare(self.version, '>=18.0'):
              args = ['/FS'] + args
          return args
  
      def get_link_debugfile_args(self, targetfile):
          pdbarr = targetfile.split('.')[:-1]
          pdbarr += ['pdb']
          return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)]
  
      def get_link_whole_for(self, args):
          # Only since VS2015
          args = listify(args)
          return ['/WHOLEARCHIVE:' + x for x in args]
  
      def get_instruction_set_args(self, instruction_set):
          if self.is_64:
              return vs64_instruction_set_args.get(instruction_set, None)
          if self.version.split('.')[0] == '16' and instruction_set == 'avx':
              # VS documentation says that this exists and should work, but
              # it does not. The headers do not contain AVX intrinsics
              # and the can not be called.
              return None
          return vs32_instruction_set_args.get(instruction_set, None)
  
      def get_toolset_version(self):
          # See boost/config/compiler/visualc.cpp for up to date mapping
          try:
              version = int(''.join(self.version.split('.')[0:2]))
          except ValueError:
              return None
          if version < 1310:
              return '7.0'
          elif version < 1400:
              return '7.1' # (Visual Studio 2003)
          elif version < 1500:
              return '8.0' # (Visual Studio 2005)
          elif version < 1600:
              return '9.0' # (Visual Studio 2008)
          elif version < 1700:
              return '10.0' # (Visual Studio 2010)
          elif version < 1800:
              return '11.0' # (Visual Studio 2012)
          elif version < 1900:
              return '12.0' # (Visual Studio 2013)
          elif version < 1910:
              return '14.0' # (Visual Studio 2015)
          elif version < 1920:
              return '14.1' # (Visual Studio 2017)
          return None
  
      def get_default_include_dirs(self):
          if 'INCLUDE' not in os.environ:
              return []
          return os.environ['INCLUDE'].split(os.pathsep)
  
      def get_crt_compile_args(self, crt_val, buildtype):
          if crt_val in self.crt_args:
              return self.crt_args[crt_val]
          assert(crt_val == 'from_buildtype')
          # Match what build type flags used to do.
          if buildtype == 'plain':
              return []
          elif buildtype == 'debug':
              return self.crt_args['mdd']
          elif buildtype == 'debugoptimized':
              return self.crt_args['md']
          elif buildtype == 'release':
              return self.crt_args['md']
          elif buildtype == 'minsize':
              return self.crt_args['md']
          else:
              assert(buildtype == 'custom')
              raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
  
      def has_func_attribute(self, name, env):
          # MSVC doesn't have __attribute__ like Clang and GCC do, so just return
          # false without compiling anything
          return name in ['dllimport', 'dllexport']
  
  
  class ArmCCompiler(ArmCompiler, CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmCompiler.__init__(self)
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c90', 'c99'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('--' + std.value)
          return args
  
files: mesonbuild/compilers/c.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import re
  import glob
  import os.path
  import subprocess
  import functools
  import itertools
  from pathlib import Path
  
  from .. import mlog
  from .. import coredata
  from . import compilers
  from ..mesonlib import (
      EnvironmentException, MesonException, version_compare, Popen_safe, listify,
      for_windows, for_darwin, for_cygwin, for_haiku, for_openbsd,
  )
  from .c_function_attributes import C_FUNC_ATTRIBUTES
  
  from .compilers import (
      GCC_MINGW,
      get_largefile_args,
      gnu_winlibs,
      msvc_winlibs,
      vs32_instruction_set_args,
      vs64_instruction_set_args,
      ArmCompiler,
      ArmclangCompiler,
      ClangCompiler,
      Compiler,
      CompilerArgs,
      CrossNoRunException,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
      RunResult,
  )
  
  gnu_compiler_internal_libs = ('m', 'c', 'pthread', 'dl', 'rt')
  
  
  class CCompiler(Compiler):
      # TODO: Replace this manual cache with functools.lru_cache
      library_dirs_cache = {}
      program_dirs_cache = {}
      find_library_cache = {}
      internal_libs = gnu_compiler_internal_libs
  
      @staticmethod
      def attribute_check_func(name):
          try:
              return C_FUNC_ATTRIBUTES[name]
          except KeyError:
              raise MesonException('Unknown function attribute "{}"'.format(name))
  
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          # If a child ObjC or CPP class has already set it, don't set it ourselves
          if not hasattr(self, 'language'):
              self.language = 'c'
          super().__init__(exelist, version, **kwargs)
          self.id = 'unknown'
          self.is_cross = is_cross
          self.can_compile_suffixes.add('h')
          # If the exe wrapper was not found, pretend it wasn't set so that the
          # sanity check is skipped and compiler checks use fallbacks.
          if not exe_wrapper or not exe_wrapper.found():
              self.exe_wrapper = None
          else:
              self.exe_wrapper = exe_wrapper.get_command()
  
          # Set to None until we actually need to check this
          self.has_fatal_warnings_link_arg = None
  
      def needs_static_linker(self):
          return True # When compiling static libraries, so yes.
  
      def get_always_args(self):
          '''
          Args that are always-on for all C compilers other than MSVC
          '''
          return ['-pipe'] + get_largefile_args(self)
  
      def get_linker_debug_crt_args(self):
          """
          Arguments needed to select a debug crt for the linker
          This is only needed for MSVC
          """
          return []
  
      def get_no_stdinc_args(self):
          return ['-nostdinc']
  
      def get_no_stdlib_link_args(self):
          return ['-nostdlib']
  
      def get_warn_args(self, level):
          return self.warn_args[level]
  
      def get_no_warn_args(self):
          # Almost every compiler uses this for disabling warnings
          return ['-w']
  
      def get_soname_args(self, *args):
          return []
  
      def split_shlib_to_parts(self, fname):
          return None, fname
  
      # The default behavior is this, override in MSVC
      @functools.lru_cache(maxsize=None)
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if self.id == 'clang' and self.clang_type == compilers.CLANG_OSX:
              return self.build_osx_rpath_args(build_dir, rpath_paths, build_rpath)
          return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return ['-MD', '-MQ', outtarget, '-MF', outfile]
  
      def depfile_for_object(self, objfile):
          return objfile + '.' + self.get_depfile_suffix()
  
      def get_depfile_suffix(self):
          return 'd'
  
      def get_exelist(self):
          return self.exelist[:]
  
      def get_linker_exelist(self):
          return self.exelist[:]
  
      def get_preprocess_only_args(self):
          return ['-E', '-P']
  
      def get_compile_only_args(self):
          return ['-c']
  
      def get_no_optimization_args(self):
          return ['-O0']
  
      def get_compiler_check_args(self):
          '''
          Get arguments useful for compiler checks such as being permissive in
          the code quality and not doing any optimization.
          '''
          return self.get_no_optimization_args()
  
      def get_allow_undefined_link_args(self):
          '''
          Get args for allowing undefined symbols when linking to a shared library
          '''
          if self.id == 'clang':
              if self.clang_type == compilers.CLANG_OSX:
                  # Apple ld
                  return ['-Wl,-undefined,dynamic_lookup']
              else:
                  # GNU ld and LLVM lld
                  return ['-Wl,--allow-shlib-undefined']
          elif self.id == 'gcc':
              if self.gcc_type == compilers.GCC_OSX:
                  # Apple ld
                  return ['-Wl,-undefined,dynamic_lookup']
              else:
                  # GNU ld and LLVM lld
                  return ['-Wl,--allow-shlib-undefined']
          elif self.id == 'msvc':
              # link.exe
              return ['/FORCE:UNRESOLVED']
          # FIXME: implement other linkers
          return []
  
      def get_output_args(self, target):
          return ['-o', target]
  
      def get_linker_output_args(self, outputname):
          return ['-o', outputname]
  
      def get_coverage_args(self):
          return ['--coverage']
  
      def get_coverage_link_args(self):
          return ['--coverage']
  
      def get_werror_args(self):
          return ['-Werror']
  
      def get_std_exe_link_args(self):
          return []
  
      def get_include_args(self, path, is_system):
          if path == '':
              path = '.'
          if is_system:
              return ['-isystem', path]
          return ['-I' + path]
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      @functools.lru_cache()
      def _get_search_dirs(self, env):
          extra_args = ['--print-search-dirs']
          stdo = None
          with self._build_wrapper('', env, extra_args, None, 'compile', True) as p:
              stdo = p.stdo
          return stdo
  
      @staticmethod
      def _split_fetch_real_dirs(pathstr, sep=':'):
          paths = []
          for p in pathstr.split(sep):
              p = Path(p)
              if p.exists():
                  paths.append(p.resolve().as_posix())
          return tuple(paths)
  
      def get_compiler_dirs(self, env, name):
          '''
          Get dirs from the compiler, either `libraries:` or `programs:`
          '''
          stdo = self._get_search_dirs(env)
          for line in stdo.split('\n'):
              if line.startswith(name + ':'):
                  return CCompiler._split_fetch_real_dirs(line.split('=', 1)[1])
          return ()
  
      @functools.lru_cache()
      def get_library_dirs(self, env):
          return self.get_compiler_dirs(env, 'libraries')
  
      @functools.lru_cache()
      def get_program_dirs(self, env):
          '''
          Programs used by the compiler. Also where toolchain DLLs such as
          libstdc++-6.dll are found with MinGW.
          '''
          return self.get_compiler_dirs(env, 'programs')
  
      def get_pic_args(self):
          return ['-fPIC']
  
      def name_string(self):
          return ' '.join(self.exelist)
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-include', os.path.basename(header)]
  
      def get_pch_name(self, header_name):
          return os.path.basename(header_name) + '.' + self.get_pch_suffix()
  
      def get_linker_search_args(self, dirname):
          return ['-L' + dirname]
  
      def get_default_include_dirs(self):
          return []
  
      def gen_export_dynamic_link_args(self, env):
          if for_windows(env.is_cross_build(), env) or for_cygwin(env.is_cross_build(), env):
              return ['-Wl,--export-all-symbols']
          elif for_darwin(env.is_cross_build(), env):
              return []
          else:
              return ['-Wl,-export-dynamic']
  
      def gen_import_library_args(self, implibname):
          """
          The name of the outputted import library
  
          This implementation is used only on Windows by compilers that use GNU ld
          """
          return ['-Wl,--out-implib=' + implibname]
  
      def sanity_check_impl(self, work_dir, environment, sname, code):
          mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist))
          mlog.debug('Is cross compiler: %s.' % str(self.is_cross))
  
          extra_flags = []
          source_name = os.path.join(work_dir, sname)
          binname = sname.rsplit('.', 1)[0]
          if self.is_cross:
              binname += '_cross'
              if self.exe_wrapper is None:
                  # Linking cross built apps is painful. You can't really
                  # tell if you should use -nostdlib or not and for example
                  # on OSX the compiler binary is the same but you need
                  # a ton of compiler flags to differentiate between
                  # arm and x86_64. So just compile.
                  extra_flags += self.get_cross_extra_flags(environment, link=False)
                  extra_flags += self.get_compile_only_args()
              else:
                  extra_flags += self.get_cross_extra_flags(environment, link=True)
          # Is a valid executable output for all toolchains and platforms
          binname += '.exe'
          # Write binary check source
          binary_name = os.path.join(work_dir, binname)
          with open(source_name, 'w') as ofile:
              ofile.write(code)
          # Compile sanity check
          cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name)
          pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir)
          mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist))
          mlog.debug('Sanity check compile stdout:')
          mlog.debug(stdo)
          mlog.debug('-----\nSanity check compile stderr:')
          mlog.debug(stde)
          mlog.debug('-----')
          if pc.returncode != 0:
              raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string()))
          # Run sanity check
          if self.is_cross:
              if self.exe_wrapper is None:
                  # Can't check if the binaries run so we have to assume they do
                  return
              cmdlist = self.exe_wrapper + [binary_name]
          else:
              cmdlist = [binary_name]
          mlog.debug('Running test binary command: ' + ' '.join(cmdlist))
          try:
              pe = subprocess.Popen(cmdlist)
          except Exception as e:
              raise EnvironmentException('Could not invoke sanity test executable: %s.' % str(e))
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string()))
  
      def sanity_check(self, work_dir, environment):
          code = 'int main(int argc, char **argv) { int class=0; return class; }\n'
          return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code)
  
      def check_header(self, hname, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname}
          code = '''{prefix}
          #include <{header}>'''
          return self.compiles(code.format(**fargs), env, extra_args, dependencies)
  
      def has_header(self, hname, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname}
          code = '''{prefix}
          #ifdef __has_include
           #if !__has_include("{header}")
            #error "Header '{header}' could not be found"
           #endif
          #else
           #include <{header}>
          #endif'''
          return self.compiles(code.format(**fargs), env, extra_args,
                               dependencies, 'preprocess')
  
      def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None):
          fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol}
          t = '''{prefix}
          #include <{header}>
          int main () {{
              /* If it's not defined as a macro, try to use as a symbol */
              #ifndef {symbol}
                  {symbol};
              #endif
          }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          if extra_args is None:
              extra_args = []
          elif isinstance(extra_args, str):
              extra_args = [extra_args]
          if dependencies is None:
              dependencies = []
          elif not isinstance(dependencies, list):
              dependencies = [dependencies]
          # Collect compiler arguments
          args = CompilerArgs(self)
          for d in dependencies:
              # Add compile flags needed by dependencies
              args += d.get_compile_args()
              if d.need_threads():
                  args += self.thread_flags(env)
              elif d.need_openmp():
                  args += self.openmp_flags()
              if mode == 'link':
                  # Add link flags needed to find dependencies
                  args += d.get_link_args()
                  if d.need_threads():
                      args += self.thread_link_flags(env)
          # Select a CRT if needed since we're linking
          if mode == 'link':
              args += self.get_linker_debug_crt_args()
          # Read c_args/cpp_args/etc from the cross-info file (if needed)
          args += self.get_cross_extra_flags(env, link=(mode == 'link'))
          if not self.is_cross:
              if mode == 'preprocess':
                  # Add CPPFLAGS from the env.
                  args += env.coredata.get_external_preprocess_args(self.language)
              elif mode == 'compile':
                  # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env
                  args += env.coredata.get_external_args(self.language)
              elif mode == 'link':
                  # Add LDFLAGS from the env
                  args += env.coredata.get_external_link_args(self.language)
          args += self.get_compiler_check_args()
          # extra_args must override all other arguments, so we add them last
          args += extra_args
          return args
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          with self._build_wrapper(code, env, extra_args, dependencies, mode) as p:
              return p.returncode == 0
  
      def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False):
          args = self._get_compiler_check_args(env, extra_args, dependencies, mode)
          return self.compile(code, args, mode, want_output=want_output)
  
      def links(self, code, env, extra_args=None, dependencies=None):
          return self.compiles(code, env, extra_args, dependencies, mode='link')
  
      def run(self, code, env, extra_args=None, dependencies=None):
          if self.is_cross and self.exe_wrapper is None:
              raise CrossNoRunException('Can not run test applications in this cross environment.')
          with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p:
              if p.returncode != 0:
                  mlog.debug('Could not compile test file %s: %d\n' % (
                      p.input_name,
                      p.returncode))
                  return RunResult(False)
              if self.is_cross:
                  cmdlist = self.exe_wrapper + [p.output_name]
              else:
                  cmdlist = p.output_name
              try:
                  pe, so, se = Popen_safe(cmdlist)
              except Exception as e:
                  mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e))
                  return RunResult(False)
  
          mlog.debug('Program stdout:\n')
          mlog.debug(so)
          mlog.debug('Program stderr:\n')
          mlog.debug(se)
          return RunResult(True, pe.returncode, so, se)
  
      def _compile_int(self, expression, prefix, env, extra_args, dependencies):
          fargs = {'prefix': prefix, 'expression': expression}
          t = '''#include <stdio.h>
          {prefix}
          int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def cross_compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies):
          # Try user's guess first
          if isinstance(guess, int):
              if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies):
                  return guess
  
          # If no bounds are given, compute them in the limit of int32
          maxint = 0x7fffffff
          minint = -0x80000000
          if not isinstance(low, int) or not isinstance(high, int):
              if self._compile_int('%s >= 0' % (expression), prefix, env, extra_args, dependencies):
                  low = cur = 0
                  while self._compile_int('%s > %d' % (expression, cur), prefix, env, extra_args, dependencies):
                      low = cur + 1
                      if low > maxint:
                          raise EnvironmentException('Cross-compile check overflowed')
                      cur = cur * 2 + 1
                      if cur > maxint:
                          cur = maxint
                  high = cur
              else:
                  low = cur = -1
                  while self._compile_int('%s < %d' % (expression, cur), prefix, env, extra_args, dependencies):
                      high = cur - 1
                      if high < minint:
                          raise EnvironmentException('Cross-compile check overflowed')
                      cur = cur * 2
                      if cur < minint:
                          cur = minint
                  low = cur
          else:
              # Sanity check limits given by user
              if high < low:
                  raise EnvironmentException('high limit smaller than low limit')
              condition = '%s <= %d && %s >= %d' % (expression, high, expression, low)
              if not self._compile_int(condition, prefix, env, extra_args, dependencies):
                  raise EnvironmentException('Value out of given range')
  
          # Binary search
          while low != high:
              cur = low + int((high - low) / 2)
              if self._compile_int('%s <= %d' % (expression, cur), prefix, env, extra_args, dependencies):
                  high = cur
              else:
                  low = cur + 1
  
          return low
  
      def compute_int(self, expression, low, high, guess, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          if self.is_cross:
              return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies)
          fargs = {'prefix': prefix, 'expression': expression}
          t = '''#include<stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              printf("%ld\\n", (long)({expression}));
              return 0;
          }};'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              return -1
          if res.returncode != 0:
              raise EnvironmentException('Could not run compute_int test binary.')
          return int(res.stdout)
  
      def cross_sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              {type} something;
          }}'''
          if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
              return -1
          return self.cross_compute_int('sizeof(%s)' % typename, None, None, None, prefix, env, extra_args, dependencies)
  
      def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          if self.is_cross:
              return self.cross_sizeof(typename, prefix, env, extra_args, dependencies)
          t = '''#include<stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              printf("%ld\\n", (long)(sizeof({type})));
              return 0;
          }};'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              return -1
          if res.returncode != 0:
              raise EnvironmentException('Could not run sizeof test binary.')
          return int(res.stdout)
  
      def cross_alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          {prefix}
          int main(int argc, char **argv) {{
              {type} something;
          }}'''
          if not self.compiles(t.format(**fargs), env, extra_args, dependencies):
              return -1
          t = '''#include <stddef.h>
          {prefix}
          struct tmp {{
              char c;
              {type} target;
          }};'''
          return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t.format(**fargs), env, extra_args, dependencies)
  
      def alignment(self, typename, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          if self.is_cross:
              return self.cross_alignment(typename, prefix, env, extra_args, dependencies)
          fargs = {'prefix': prefix, 'type': typename}
          t = '''#include <stdio.h>
          #include <stddef.h>
          {prefix}
          struct tmp {{
              char c;
              {type} target;
          }};
          int main(int argc, char **argv) {{
              printf("%d", (int)offsetof(struct tmp, target));
              return 0;
          }}'''
          res = self.run(t.format(**fargs), env, extra_args, dependencies)
          if not res.compiled:
              raise EnvironmentException('Could not compile alignment test.')
          if res.returncode != 0:
              raise EnvironmentException('Could not run alignment test binary.')
          align = int(res.stdout)
          if align == 0:
              raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename)
          return align
  
      def get_define(self, dname, prefix, env, extra_args, dependencies):
          delim = '"MESON_GET_DEFINE_DELIMITER"'
          fargs = {'prefix': prefix, 'define': dname, 'delim': delim}
          code = '''
          {prefix}
          #ifndef {define}
          # define {define}
          #endif
          {delim}\n{define}'''
          args = self._get_compiler_check_args(env, extra_args, dependencies,
                                               mode='preprocess').to_native()
          with self.compile(code.format(**fargs), args, 'preprocess') as p:
              if p.returncode != 0:
                  raise EnvironmentException('Could not get define {!r}'.format(dname))
          # Get the preprocessed value after the delimiter,
          # minus the extra newline at the end and
          # merge string literals.
          return CCompiler.concatenate_string_literals(p.stdo.split(delim + '\n')[-1][:-1])
  
      def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies):
          if rtype == 'string':
              fmt = '%s'
              cast = '(char*)'
          elif rtype == 'int':
              fmt = '%lli'
              cast = '(long long int)'
          else:
              raise AssertionError('BUG: Unknown return type {!r}'.format(rtype))
          fargs = {'prefix': prefix, 'f': fname, 'cast': cast, 'fmt': fmt}
          code = '''{prefix}
          #include <stdio.h>
          int main(int argc, char *argv[]) {{
              printf ("{fmt}", {cast} {f}());
          }}'''.format(**fargs)
          res = self.run(code, env, extra_args, dependencies)
          if not res.compiled:
              m = 'Could not get return value of {}()'
              raise EnvironmentException(m.format(fname))
          if rtype == 'string':
              return res.stdout
          elif rtype == 'int':
              try:
                  return int(res.stdout.strip())
              except ValueError:
                  m = 'Return value of {}() is not an int'
                  raise EnvironmentException(m.format(fname))
  
      @staticmethod
      def _no_prototype_templ():
          """
          Try to find the function without a prototype from a header by defining
          our own dummy prototype and trying to link with the C library (and
          whatever else the compiler links in by default). This is very similar
          to the check performed by Autoconf for AC_CHECK_FUNCS.
          """
          # Define the symbol to something else since it is defined by the
          # includes or defines listed by the user or by the compiler. This may
          # include, for instance _GNU_SOURCE which must be defined before
          # limits.h, which includes features.h
          # Then, undef the symbol to get rid of it completely.
          head = '''
          #define {func} meson_disable_define_of_{func}
          {prefix}
          #include <limits.h>
          #undef {func}
          '''
          # Override any GCC internal prototype and declare our own definition for
          # the symbol. Use char because that's unlikely to be an actual return
          # value for a function which ensures that we override the definition.
          head += '''
          #ifdef __cplusplus
          extern "C"
          #endif
          char {func} ();
          '''
          # The actual function call
          main = '''
          int main () {{
            return {func} ();
          }}'''
          return head, main
  
      @staticmethod
      def _have_prototype_templ():
          """
          Returns a head-er and main() call that uses the headers listed by the
          user for the function prototype while checking if a function exists.
          """
          # Add the 'prefix', aka defines, includes, etc that the user provides
          # This may include, for instance _GNU_SOURCE which must be defined
          # before limits.h, which includes features.h
          head = '{prefix}\n#include <limits.h>\n'
          # We don't know what the function takes or returns, so return it as an int.
          # Just taking the address or comparing it to void is not enough because
          # compilers are smart enough to optimize it away. The resulting binary
          # is not run so we don't care what the return value is.
          main = '''\nint main() {{
              void *a = (void*) &{func};
              long b = (long) a;
              return (int) b;
          }}'''
          return head, main
  
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          """
          First, this function looks for the symbol in the default libraries
          provided by the compiler (stdlib + a few others usually). If that
          fails, it checks if any of the headers specified in the prefix provide
          an implementation of the function, and if that fails, it checks if it's
          implemented as a compiler-builtin.
          """
          if extra_args is None:
              extra_args = []
  
          # Short-circuit if the check is already provided by the cross-info file
          varname = 'has function ' + funcname
          varname = varname.replace(' ', '_')
          if self.is_cross:
              val = env.cross_info.config['properties'].get(varname, None)
              if val is not None:
                  if isinstance(val, bool):
                      return val
                  raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname))
  
          fargs = {'prefix': prefix, 'func': funcname}
  
          # glibc defines functions that are not available on Linux as stubs that
          # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail
          # instead of detecting the stub as a valid symbol.
          # We already included limits.h earlier to ensure that these are defined
          # for stub functions.
          stubs_fail = '''
          #if defined __stub_{func} || defined __stub___{func}
          fail fail fail this function is not going to work
          #endif
          '''
  
          # If we have any includes in the prefix supplied by the user, assume
          # that the user wants us to use the symbol prototype defined in those
          # includes. If not, then try to do the Autoconf-style check with
          # a dummy prototype definition of our own.
          # This is needed when the linker determines symbol availability from an
          # SDK based on the prototype in the header provided by the SDK.
          # Ignoring this prototype would result in the symbol always being
          # marked as available.
          if '#include' in prefix:
              head, main = self._have_prototype_templ()
          else:
              head, main = self._no_prototype_templ()
          templ = head + stubs_fail + main
  
          if self.links(templ.format(**fargs), env, extra_args, dependencies):
              return True
  
          # MSVC does not have compiler __builtin_-s.
          if self.get_id() == 'msvc':
              return False
  
          # Detect function as a built-in
          #
          # Some functions like alloca() are defined as compiler built-ins which
          # are inlined by the compiler and you can't take their address, so we
          # need to look for them differently. On nice compilers like clang, we
          # can just directly use the __has_builtin() macro.
          fargs['no_includes'] = '#include' not in prefix
          t = '''{prefix}
          int main() {{
          #ifdef __has_builtin
              #if !__has_builtin(__builtin_{func})
                  #error "__builtin_{func} not found"
              #endif
          #elif ! defined({func})
              /* Check for __builtin_{func} only if no includes were added to the
               * prefix above, which means no definition of {func} can be found.
               * We would always check for this, but we get false positives on
               * MSYS2 if we do. Their toolchain is broken, but we can at least
               * give them a workaround. */
              #if {no_includes:d}
                  __builtin_{func};
              #else
                  #error "No definition for __builtin_{func} found in the prefix"
              #endif
          #endif
          }}'''
          return self.links(t.format(**fargs), env, extra_args, dependencies)
  
      def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'type': typename, 'name': 'foo'}
          # Create code that accesses all members
          members = ''
          for member in membernames:
              members += '{}.{};\n'.format(fargs['name'], member)
          fargs['members'] = members
          t = '''{prefix}
          void bar() {{
              {type} {name};
              {members}
          }};'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def has_type(self, typename, prefix, env, extra_args, dependencies=None):
          fargs = {'prefix': prefix, 'type': typename}
          t = '''{prefix}
          void bar() {{
              sizeof({type});
          }};'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def symbols_have_underscore_prefix(self, env):
          '''
          Check if the compiler prefixes an underscore to global C symbols
          '''
          symbol_name = b'meson_uscore_prefix'
          code = '''#ifdef __cplusplus
          extern "C" {
          #endif
          void ''' + symbol_name.decode() + ''' () {}
          #ifdef __cplusplus
          }
          #endif
          '''
          args = self.get_cross_extra_flags(env, link=False)
          args += self.get_compiler_check_args()
          n = 'symbols_have_underscore_prefix'
          with self.compile(code, args, 'compile', want_output=True) as p:
              if p.returncode != 0:
                  m = 'BUG: Unable to compile {!r} check: {}'
                  raise RuntimeError(m.format(n, p.stdo))
              if not os.path.isfile(p.output_name):
                  m = 'BUG: Can\'t find compiled test code for {!r} check'
                  raise RuntimeError(m.format(n))
              with open(p.output_name, 'rb') as o:
                  for line in o:
                      # Check if the underscore form of the symbol is somewhere
                      # in the output file.
                      if b'_' + symbol_name in line:
                          return True
                      # Else, check if the non-underscored form is present
                      elif symbol_name in line:
                          return False
          raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n))
  
      def _get_patterns(self, env, prefixes, suffixes, shared=False):
          patterns = []
          for p in prefixes:
              for s in suffixes:
                  patterns.append(p + '{}.' + s)
          if shared and for_openbsd(self.is_cross, env):
              # Shared libraries on OpenBSD can be named libfoo.so.X.Y:
              # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs
              #
              # This globbing is probably the best matching we can do since regex
              # is expensive. It's wrong in many edge cases, but it will match
              # correctly-named libraries and hopefully no one on OpenBSD names
              # their files libfoo.so.9a.7b.1.0
              patterns.append('lib{}.so.[0-9]*.[0-9]*')
          return patterns
  
      def get_library_naming(self, env, libtype, strict=False):
          '''
          Get library prefixes and suffixes for the target platform ordered by
          priority
          '''
          stlibext = ['a']
          # We've always allowed libname to be both `foo` and `libfoo`,
          # and now people depend on it
          if strict and self.id != 'msvc': # lib prefix is not usually used with msvc
              prefixes = ['lib']
          else:
              prefixes = ['lib', '']
          # Library suffixes and prefixes
          if for_darwin(env.is_cross_build(), env):
              shlibext = ['dylib']
          elif for_windows(env.is_cross_build(), env):
              # FIXME: .lib files can be import or static so we should read the
              # file, figure out which one it is, and reject the wrong kind.
              if self.id == 'msvc':
                  shlibext = ['lib']
              else:
                  shlibext = ['dll.a', 'lib', 'dll']
              # Yep, static libraries can also be foo.lib
              stlibext += ['lib']
          elif for_cygwin(env.is_cross_build(), env):
              shlibext = ['dll', 'dll.a']
              prefixes = ['cyg'] + prefixes
          else:
              # Linux/BSDs
              shlibext = ['so']
          patterns = []
          # Search priority
          if libtype in ('default', 'shared-static'):
              patterns += self._get_patterns(env, prefixes, shlibext, True)
              patterns += self._get_patterns(env, prefixes, stlibext, False)
          elif libtype == 'static-shared':
              patterns += self._get_patterns(env, prefixes, stlibext, False)
              patterns += self._get_patterns(env, prefixes, shlibext, True)
          elif libtype == 'shared':
              patterns += self._get_patterns(env, prefixes, shlibext, True)
          elif libtype == 'static':
              patterns += self._get_patterns(env, prefixes, stlibext, False)
          else:
              raise AssertionError('BUG: unknown libtype {!r}'.format(libtype))
          return tuple(patterns)
  
      @staticmethod
      def _sort_shlibs_openbsd(libs):
          filtered = []
          for lib in libs:
              # Validate file as a shared library of type libfoo.so.X.Y
              ret = lib.rsplit('.so.', maxsplit=1)
              if len(ret) != 2:
                  continue
              try:
                  float(ret[1])
              except ValueError:
                  continue
              filtered.append(lib)
          float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1])
          return sorted(filtered, key=float_cmp, reverse=True)
  
      @classmethod
      def _get_trials_from_pattern(cls, pattern, directory, libname):
          f = Path(directory) / pattern.format(libname)
          # Globbing for OpenBSD
          if '*' in pattern:
              # NOTE: globbing matches directories and broken symlinks
              # so we have to do an isfile test on it later
              return cls._sort_shlibs_openbsd(glob.glob(str(f)))
          return [f.as_posix()]
  
      @staticmethod
      def _get_file_from_list(files):
          for f in files:
              if os.path.isfile(f):
                  return f
          return None
  
      def find_library_real(self, libname, env, extra_dirs, code, libtype):
          # First try if we can just add the library as -l.
          # Gcc + co seem to prefer builtin lib dirs to -L dirs.
          # Only try to find std libs if no extra dirs specified.
          if not extra_dirs or libname in self.internal_libs:
              args = ['-l' + libname]
              largs = self.linker_to_compiler_args(self.get_allow_undefined_link_args())
              if self.links(code, env, extra_args=(args + largs)):
                  return args
              # Don't do a manual search for internal libs
              if libname in self.internal_libs:
                  return None
          # Not found or we want to use a specific libtype? Try to find the
          # library file itself.
          patterns = self.get_library_naming(env, libtype)
          # Search in the specified dirs, and then in the system libraries
          for d in itertools.chain(extra_dirs, self.get_library_dirs(env)):
              for p in patterns:
                  trial = self._get_trials_from_pattern(p, d, libname)
                  if not trial:
                      continue
                  # We just check whether the library exists. We can't do a link
                  # check because the library might have unresolved symbols that
                  # require other libraries.
                  trial = self._get_file_from_list(trial)
                  if not trial:
                      continue
                  return [trial]
          return None
  
      def find_library_impl(self, libname, env, extra_dirs, code, libtype):
          # These libraries are either built-in or invalid
          if libname in self.ignore_libs:
              return []
          if isinstance(extra_dirs, str):
              extra_dirs = [extra_dirs]
          key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype)
          if key not in self.find_library_cache:
              value = self.find_library_real(libname, env, extra_dirs, code, libtype)
              self.find_library_cache[key] = value
          else:
              value = self.find_library_cache[key]
          if value is None:
              return None
          return value[:]
  
      def find_library(self, libname, env, extra_dirs, libtype='default'):
          code = 'int main(int argc, char **argv) { return 0; }'
          return self.find_library_impl(libname, env, extra_dirs, code, libtype)
  
      def thread_flags(self, env):
          if for_haiku(self.is_cross, env):
              return []
          return ['-pthread']
  
      def thread_link_flags(self, env):
          if for_haiku(self.is_cross, env):
              return []
          return ['-pthread']
  
      def linker_to_compiler_args(self, args):
          return args
  
      def has_arguments(self, args, env, code, mode):
          return self.compiles(code, env, extra_args=args, mode=mode)
  
      def has_multi_arguments(self, args, env):
          for arg in args[:]:
              # some compilers, e.g. GCC, don't warn for unsupported warning-disable
              # flags, so when we are testing a flag like "-Wno-forgotten-towel", also
              # check the equivalent enable flag too "-Wforgotten-towel"
              if arg.startswith('-Wno-'):
                      args.append('-W' + arg[5:])
              if arg.startswith('-Wl,'):
                  mlog.warning('{} looks like a linker argument, '
                               'but has_argument and other similar methods only '
                               'support checking compiler arguments. Using them '
                               'to check linker arguments are never supported, '
                               'and results are likely to be wrong regardless of '
                               'the compiler you are using. has_link_argument or '
                               'other similar method can be used instead.'
                               .format(arg))
          code = 'int i;\n'
          return self.has_arguments(args, env, code, mode='compile')
  
      def has_multi_link_arguments(self, args, env):
          # First time we check for link flags we need to first check if we have
          # --fatal-warnings, otherwise some linker checks could give some
          # false positive.
          fatal_warnings_args = ['-Wl,--fatal-warnings']
          if self.has_fatal_warnings_link_arg is None:
              self.has_fatal_warnings_link_arg = False
              self.has_fatal_warnings_link_arg = self.has_multi_link_arguments(fatal_warnings_args, env)
  
          if self.has_fatal_warnings_link_arg:
              args = fatal_warnings_args + args
  
          args = self.linker_to_compiler_args(args)
          code = 'int main(int argc, char **argv) { return 0; }'
          return self.has_arguments(args, env, code, mode='link')
  
      @staticmethod
      def concatenate_string_literals(s):
          pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)')
          ret = s
          m = pattern.match(ret)
          while m:
              ret = ''.join(m.group('pre', 'str1', 'str2', 'post'))
              m = pattern.match(ret)
          return ret
  
      def has_func_attribute(self, name, env):
          # Just assume that if we're not on windows that dllimport and dllexport
          # don't work
          if not (for_windows(env.is_cross_build(), env) or
                  for_cygwin(env.is_cross_build(), env)):
              if name in ['dllimport', 'dllexport']:
                  return False
  
          # Clang and GCC both return warnings if the __attribute__ is undefined,
          # so set -Werror
          return self.compiles(self.attribute_check_func(name), env, extra_args='-Werror')
  
  
  class ClangCCompiler(ClangCompiler, CCompiler):
      def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ClangCompiler.__init__(self, clang_type)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c99', 'c11',
                                                          'gnu89', 'gnu99', 'gnu11'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def get_linker_always_args(self):
          basic = super().get_linker_always_args()
          if self.clang_type == compilers.CLANG_OSX:
              return basic + ['-Wl,-headerpad_max_install_names']
          return basic
  
  
  class ArmclangCCompiler(ArmclangCompiler, CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmclangCompiler.__init__(self)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c90', 'c99', 'c11',
                                                          'gnu90', 'gnu99', 'gnu11'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class GnuCCompiler(GnuCompiler, CCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          GnuCompiler.__init__(self, gcc_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c99', 'c11',
                                                          'gnu89', 'gnu99', 'gnu11'],
                                                         'none')})
          if self.gcc_type == GCC_MINGW:
              opts.update({
                  'c_winlibs': coredata.UserArrayOption('c_winlibs', 'Standard Win libraries to link against',
                                                        gnu_winlibs), })
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          if self.gcc_type == GCC_MINGW:
              return options['c_winlibs'].value[:]
          return []
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-fpch-preprocess', '-include', os.path.basename(header)]
  
  
  class ElbrusCCompiler(GnuCCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuCCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, gcc_type, defines)
  
      # It does support some various ISO standards and c/gnu 90, 9x, 1x in addition to those which GNU CC supports.
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11',
                                                          'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11',
                                                          'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999'],
                                                         'none')})
          return opts
  
      # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error.
      # So we should explicitly fail at this case.
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if funcname == 'lchmod':
              return False
          else:
              return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
  
  class IntelCCompiler(IntelCompiler, CCompiler):
      def __init__(self, exelist, version, icc_type, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          IntelCompiler.__init__(self, icc_type)
          self.lang_header = 'c-header'
          default_warn_args = ['-Wall', '-w3', '-diag-disable:remark', '-Wpch-messages']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          c_stds = ['c89', 'c99']
          g_stds = ['gnu89', 'gnu99']
          if version_compare(self.version, '>=16.0.0'):
              c_stds += ['c11']
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none'] + c_stds + g_stds,
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def has_arguments(self, args, env, code, mode):
          return super().has_arguments(args + ['-diag-error', '10006'], env, code, mode)
  
  
  class VisualStudioCCompiler(CCompiler):
      std_warn_args = ['/W3']
      std_opt_args = ['/O2']
      ignore_libs = gnu_compiler_internal_libs
      internal_libs = ()
  
      crt_args = {'none': [],
                  'md': ['/MD'],
                  'mdd': ['/MDd'],
                  'mt': ['/MT'],
                  'mtd': ['/MTd'],
                  }
  
      def __init__(self, exelist, version, is_cross, exe_wrap, is_64):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
          self.id = 'msvc'
          # /showIncludes is needed for build dependency tracking in Ninja
          # See: https://ninja-build.org/manual.html#_deps
          self.always_args = ['/nologo', '/showIncludes']
          self.warn_args = {'1': ['/W2'],
                            '2': ['/W3'],
                            '3': ['/W4']}
          self.base_options = ['b_pch', 'b_ndebug', 'b_vscrt'] # FIXME add lto, pgo and the like
          self.is_64 = is_64
  
      # Override CCompiler.get_always_args
      def get_always_args(self):
          return self.always_args
  
      def get_linker_debug_crt_args(self):
          """
          Arguments needed to select a debug crt for the linker
  
          Sometimes we need to manually select the CRT (C runtime) to use with
          MSVC. One example is when trying to link with static libraries since
          MSVC won't auto-select a CRT for us in that case and will error out
          asking us to select one.
          """
          return ['/MDd']
  
      def get_buildtype_args(self, buildtype):
          args = compilers.msvc_buildtype_args[buildtype]
          if version_compare(self.version, '<18.0'):
              args = [arg for arg in args if arg != '/Gw']
          return args
  
      def get_buildtype_linker_args(self, buildtype):
          return compilers.msvc_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'pch'
  
      def get_pch_name(self, header):
          chopped = os.path.basename(header).split('.')[:-1]
          chopped.append(self.get_pch_suffix())
          pchname = '.'.join(chopped)
          return pchname
  
      def get_pch_use_args(self, pch_dir, header):
          base = os.path.basename(header)
          pchname = self.get_pch_name(header)
          return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)]
  
      def get_preprocess_only_args(self):
          return ['/EP']
  
      def get_compile_only_args(self):
          return ['/c']
  
      def get_no_optimization_args(self):
          return ['/Od']
  
      def get_output_args(self, target):
          if target.endswith('.exe'):
              return ['/Fe' + target]
          return ['/Fo' + target]
  
      def get_optimization_args(self, optimization_level):
          return compilers.msvc_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return compilers.msvc_debug_args[is_debug]
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      def get_linker_exelist(self):
          return ['link'] # FIXME, should have same path as compiler.
  
      def get_linker_always_args(self):
          return ['/nologo']
  
      def get_linker_output_args(self, outputname):
          return ['/OUT:' + outputname]
  
      def get_linker_search_args(self, dirname):
          return ['/LIBPATH:' + dirname]
  
      def linker_to_compiler_args(self, args):
          return ['/link'] + args
  
      def get_gui_app_args(self, value):
          # the default is for the linker to guess the subsystem based on presence
          # of main or WinMain symbols, so always be explicit
          if value:
              return ['/SUBSYSTEM:WINDOWS']
          else:
              return ['/SUBSYSTEM:CONSOLE']
  
      def get_pic_args(self):
          return [] # PIC is handled by the loader on Windows
  
      def gen_export_dynamic_link_args(self, env):
          return [] # Not applicable with MSVC
  
      def get_std_shared_lib_link_args(self):
          return ['/DLL']
  
      def gen_vs_module_defs_args(self, defsfile):
          if not isinstance(defsfile, str):
              raise RuntimeError('Module definitions file should be str')
          # With MSVC, DLLs only export symbols that are explicitly exported,
          # so if a module defs file is specified, we use that to export symbols
          return ['/DEF:' + defsfile]
  
      def gen_pch_args(self, header, source, pchname):
          objname = os.path.splitext(pchname)[0] + '.obj'
          return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname]
  
      def gen_import_library_args(self, implibname):
          "The name of the outputted import library"
          return ['/IMPLIB:' + implibname]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return []
  
      def openmp_flags(self):
          return ['/openmp']
  
      # FIXME, no idea what these should be.
      def thread_flags(self, env):
          return []
  
      def thread_link_flags(self, env):
          return []
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_winlibs': coredata.UserArrayOption('c_winlibs',
                                                             'Windows libs to link against.',
                                                             msvc_winlibs)})
          return opts
  
      def get_option_link_args(self, options):
          return options['c_winlibs'].value[:]
  
      @classmethod
      def unix_args_to_native(cls, args):
          result = []
          for i in args:
              # -mms-bitfields is specific to MinGW-GCC
              # -pthread is only valid for GCC
              if i in ('-mms-bitfields', '-pthread'):
                  continue
              if i.startswith('-L'):
                  i = '/LIBPATH:' + i[2:]
              # Translate GNU-style -lfoo library name to the import library
              elif i.startswith('-l'):
                  name = i[2:]
                  if name in cls.ignore_libs:
                      # With MSVC, these are provided by the C runtime which is
                      # linked in by default
                      continue
                  else:
                      i = name + '.lib'
              # -pthread in link flags is only used on Linux
              elif i == '-pthread':
                  continue
              result.append(i)
          return result
  
      def get_werror_args(self):
          return ['/WX']
  
      def get_include_args(self, path, is_system):
          if path == '':
              path = '.'
          # msvc does not have a concept of system header dirs.
          return ['-I' + path]
  
      # Visual Studio is special. It ignores some arguments it does not
      # understand and you can't tell it to error out on those.
      # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t
      def has_arguments(self, args, env, code, mode):
          warning_text = '4044' if mode == 'link' else '9002'
          with self._build_wrapper(code, env, extra_args=args, mode=mode) as p:
              if p.returncode != 0:
                  return False
              return not(warning_text in p.stde or warning_text in p.stdo)
  
      def get_compile_debugfile_args(self, rel_obj, pch=False):
          pdbarr = rel_obj.split('.')[:-1]
          pdbarr += ['pdb']
          args = ['/Fd' + '.'.join(pdbarr)]
          # When generating a PDB file with PCH, all compile commands write
          # to the same PDB file. Hence, we need to serialize the PDB
          # writes using /FS since we do parallel builds. This slows down the
          # build obviously, which is why we only do this when PCH is on.
          # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was
          # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx
          if pch and version_compare(self.version, '>=18.0'):
              args = ['/FS'] + args
          return args
  
      def get_link_debugfile_args(self, targetfile):
          pdbarr = targetfile.split('.')[:-1]
          pdbarr += ['pdb']
          return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)]
  
      def get_link_whole_for(self, args):
          # Only since VS2015
          args = listify(args)
          return ['/WHOLEARCHIVE:' + x for x in args]
  
      def get_instruction_set_args(self, instruction_set):
          if self.is_64:
              return vs64_instruction_set_args.get(instruction_set, None)
          if self.version.split('.')[0] == '16' and instruction_set == 'avx':
              # VS documentation says that this exists and should work, but
              # it does not. The headers do not contain AVX intrinsics
              # and the can not be called.
              return None
          return vs32_instruction_set_args.get(instruction_set, None)
  
      def get_toolset_version(self):
          # See boost/config/compiler/visualc.cpp for up to date mapping
          try:
              version = int(''.join(self.version.split('.')[0:2]))
          except ValueError:
              return None
          if version < 1310:
              return '7.0'
          elif version < 1400:
              return '7.1' # (Visual Studio 2003)
          elif version < 1500:
              return '8.0' # (Visual Studio 2005)
          elif version < 1600:
              return '9.0' # (Visual Studio 2008)
          elif version < 1700:
              return '10.0' # (Visual Studio 2010)
          elif version < 1800:
              return '11.0' # (Visual Studio 2012)
          elif version < 1900:
              return '12.0' # (Visual Studio 2013)
          elif version < 1910:
              return '14.0' # (Visual Studio 2015)
          elif version < 1920:
              return '14.1' # (Visual Studio 2017)
          return None
  
      def get_default_include_dirs(self):
          if 'INCLUDE' not in os.environ:
              return []
          return os.environ['INCLUDE'].split(os.pathsep)
  
      def get_crt_compile_args(self, crt_val, buildtype):
          if crt_val in self.crt_args:
              return self.crt_args[crt_val]
          assert(crt_val == 'from_buildtype')
          # Match what build type flags used to do.
          if buildtype == 'plain':
              return []
          elif buildtype == 'debug':
              return self.crt_args['mdd']
          elif buildtype == 'debugoptimized':
              return self.crt_args['md']
          elif buildtype == 'release':
              return self.crt_args['md']
          elif buildtype == 'minsize':
              return self.crt_args['md']
          else:
              assert(buildtype == 'custom')
              raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
  
      def has_func_attribute(self, name, env):
          # MSVC doesn't have __attribute__ like Clang and GCC do, so just return
          # false without compiling anything
          return name in ['dllimport', 'dllexport']
  
  
  class ArmCCompiler(ArmCompiler, CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmCompiler.__init__(self)
  
      def get_options(self):
          opts = CCompiler.get_options(self)
          opts.update({'c_std': coredata.UserComboOption('c_std', 'C language standard to use',
                                                         ['none', 'c90', 'c99'],
                                                         'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['c_std']
          if std.value != 'none':
              args.append('--' + std.value)
          return args
  
files: mesonbuild/compilers/c.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import contextlib, enum, os.path, re, tempfile, shlex
  import subprocess
  
  from ..linkers import StaticLinker
  from .. import coredata
  from .. import mlog
  from .. import mesonlib
  from ..mesonlib import EnvironmentException, MesonException, OrderedSet, version_compare, Popen_safe
  
  """This file contains the data files of all compilers Meson knows
  about. To support a new compiler, add its information below.
  Also add corresponding autodetection code in environment.py."""
  
  header_suffixes = ('h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di')
  obj_suffixes = ('o', 'obj', 'res')
  lib_suffixes = ('a', 'lib', 'dll', 'dylib', 'so')
  # Mapping of language to suffixes of files that should always be in that language
  # This means we can't include .h headers here since they could be C, C++, ObjC, etc.
  lang_suffixes = {
      'c': ('c',),
      'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx'),
      # f90, f95, f03, f08 are for free-form fortran ('f90' recommended)
      # f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended)
      'fortran': ('f90', 'f95', 'f03', 'f08', 'f', 'for', 'ftn', 'fpp'),
      'd': ('d', 'di'),
      'objc': ('m',),
      'objcpp': ('mm',),
      'rust': ('rs',),
      'vala': ('vala', 'vapi', 'gs'),
      'cs': ('cs',),
      'swift': ('swift',),
      'java': ('java',),
  }
  all_languages = lang_suffixes.keys()
  cpp_suffixes = lang_suffixes['cpp'] + ('h',)
  c_suffixes = lang_suffixes['c'] + ('h',)
  # List of languages that by default consume and output libraries following the
  # C ABI; these can generally be used interchangebly
  clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran',)
  # List of languages that can be linked with C code directly by the linker
  # used in build.py:process_compilers() and build.py:get_dynamic_linker()
  # XXX: Add Rust to this?
  clink_langs = ('d',) + clib_langs
  clink_suffixes = ()
  for _l in clink_langs + ('vala',):
      clink_suffixes += lang_suffixes[_l]
  clink_suffixes += ('h', 'll', 's')
  
  soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
  
  # Environment variables that each lang uses.
  cflags_mapping = {'c': 'CFLAGS',
                    'cpp': 'CXXFLAGS',
                    'objc': 'OBJCFLAGS',
                    'objcpp': 'OBJCXXFLAGS',
                    'fortran': 'FFLAGS',
                    'd': 'DFLAGS',
                    'vala': 'VALAFLAGS',
                    'rust': 'RUSTFLAGS'}
  
  # All these are only for C-linkable languages; see `clink_langs` above.
  
  def sort_clink(lang):
      '''
      Sorting function to sort the list of languages according to
      reversed(compilers.clink_langs) and append the unknown langs in the end.
      The purpose is to prefer C over C++ for files that can be compiled by
      both such as assembly, C, etc. Also applies to ObjC, ObjC++, etc.
      '''
      if lang not in clink_langs:
          return 1
      return -clink_langs.index(lang)
  
  def is_header(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1]
      return suffix in header_suffixes
  
  def is_source(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1].lower()
      return suffix in clink_suffixes
  
  def is_assembly(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      return fname.split('.')[-1].lower() == 's'
  
  def is_llvm_ir(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      return fname.split('.')[-1] == 'll'
  
  def is_object(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1]
      return suffix in obj_suffixes
  
  def is_library(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
  
      if soregex.match(fname):
          return True
  
      suffix = fname.split('.')[-1]
      return suffix in lib_suffixes
  
  gnulike_buildtype_args = {'plain': [],
                            'debug': [],
                            'debugoptimized': [],
                            'release': [],
                            'minsize': []}
  
  armclang_buildtype_args = {'plain': [],
                             'debug': ['-O0', '-g'],
                             'debugoptimized': ['-O1', '-g'],
                             'release': ['-Os'],
                             'minsize': ['-Oz']}
  
  arm_buildtype_args = {'plain': [],
                        'debug': ['-O0', '--debug'],
                        'debugoptimized': ['-O1', '--debug'],
                        'release': ['-O3', '-Otime'],
                        'minsize': ['-O3', '-Ospace'],
                        }
  
  msvc_buildtype_args = {'plain': [],
                         'debug': ["/ZI", "/Ob0", "/Od", "/RTC1"],
                         'debugoptimized': ["/Zi", "/Ob1"],
                         'release': ["/Ob2", "/Gw"],
                         'minsize': ["/Zi", "/Gw"],
                         }
  
  apple_buildtype_linker_args = {'plain': [],
                                 'debug': [],
                                 'debugoptimized': [],
                                 'release': [],
                                 'minsize': [],
                                 }
  
  gnulike_buildtype_linker_args = {'plain': [],
                                   'debug': [],
                                   'debugoptimized': [],
                                   'release': ['-Wl,-O1'],
                                   'minsize': [],
                                   }
  
  arm_buildtype_linker_args = {'plain': [],
                               'debug': [],
                               'debugoptimized': [],
                               'release': [],
                               'minsize': [],
                               }
  
  msvc_buildtype_linker_args = {'plain': [],
                                'debug': [],
                                'debugoptimized': [],
                                # The otherwise implicit REF and ICF linker
                                # optimisations are disabled by /DEBUG.
                                # REF implies ICF.
                                'release': ['/OPT:REF'],
                                'minsize': ['/INCREMENTAL:NO', '/OPT:REF'],
                                }
  
  java_buildtype_args = {'plain': [],
                         'debug': ['-g'],
                         'debugoptimized': ['-g'],
                         'release': [],
                         'minsize': [],
                         }
  
  rust_buildtype_args = {'plain': [],
                         'debug': [],
                         'debugoptimized': [],
                         'release': [],
                         'minsize': [],
                         }
  
  d_gdc_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-finline-functions'],
                          'release': ['-frelease', '-finline-functions'],
                          'minsize': [],
                          }
  
  d_ldc_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'],
                          'release': ['-release', '-enable-inlining', '-Hkeep-all-bodies'],
                          'minsize': [],
                          }
  
  d_dmd_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-inline'],
                          'release': ['-release', '-inline'],
                          'minsize': [],
                          }
  
  mono_buildtype_args = {'plain': [],
                         'debug': [],
                         'debugoptimized': ['-optimize+'],
                         'release': ['-optimize+'],
                         'minsize': [],
                         }
  
  swift_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': [],
                          'release': [],
                          'minsize': [],
                          }
  
  gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32',
                 '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32']
  
  msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib',
                  'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib',
                  'uuid.lib', 'comdlg32.lib', 'advapi32.lib']
  
  gnu_color_args = {'auto': ['-fdiagnostics-color=auto'],
                    'always': ['-fdiagnostics-color=always'],
                    'never': ['-fdiagnostics-color=never'],
                    }
  
  clang_color_args = {'auto': ['-Xclang', '-fcolor-diagnostics'],
                      'always': ['-Xclang', '-fcolor-diagnostics'],
                      'never': ['-Xclang', '-fno-color-diagnostics'],
                      }
  
  clike_optimization_args = {'0': [],
                             'g': [],
                             '1': ['-O1'],
                             '2': ['-O2'],
                             '3': ['-O3'],
                             's': ['-Os'],
                             }
  
  gnu_optimization_args = {'0': [],
                           'g': ['-Og'],
                           '1': ['-O1'],
                           '2': ['-O2'],
                           '3': ['-O3'],
                           's': ['-Os'],
                           }
  
  msvc_optimization_args = {'0': [],
                            'g': ['/O0'],
                            '1': ['/O1'],
                            '2': ['/O2'],
                            '3': ['/O3'],
                            's': ['/O1'], # Implies /Os.
                            }
  
  clike_debug_args = {False: [],
                      True: ['-g']}
  
  msvc_debug_args = {False: [],
                     True: []} # Fixme!
  
  base_options = {'b_pch': coredata.UserBooleanOption('b_pch', 'Use precompiled headers', True),
                  'b_lto': coredata.UserBooleanOption('b_lto', 'Use link time optimization', False),
                  'b_sanitize': coredata.UserComboOption('b_sanitize',
                                                         'Code sanitizer to use',
                                                         ['none', 'address', 'thread', 'undefined', 'memory', 'address,undefined'],
                                                         'none'),
                  'b_lundef': coredata.UserBooleanOption('b_lundef', 'Use -Wl,--no-undefined when linking', True),
                  'b_asneeded': coredata.UserBooleanOption('b_asneeded', 'Use -Wl,--as-needed when linking', True),
                  'b_pgo': coredata.UserComboOption('b_pgo', 'Use profile guided optimization',
                                                    ['off', 'generate', 'use'],
                                                    'off'),
                  'b_coverage': coredata.UserBooleanOption('b_coverage',
                                                           'Enable coverage tracking.',
                                                           False),
                  'b_colorout': coredata.UserComboOption('b_colorout', 'Use colored output',
                                                         ['auto', 'always', 'never'],
                                                         'always'),
                  'b_ndebug': coredata.UserComboOption('b_ndebug', 'Disable asserts',
                                                       ['true', 'false', 'if-release'], 'false'),
                  'b_staticpic': coredata.UserBooleanOption('b_staticpic',
                                                            'Build static libraries as position independent',
                                                            True),
                  'b_bitcode': coredata.UserBooleanOption('b_bitcode',
                                                          'Generate and embed bitcode (only macOS and iOS)',
                                                          False),
                  'b_vscrt': coredata.UserComboOption('b_vscrt', 'VS run-time library type to use.',
                                                      ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype'],
                                                      'from_buildtype'),
                  }
  
  gnulike_instruction_set_args = {'mmx': ['-mmmx'],
                                  'sse': ['-msse'],
                                  'sse2': ['-msse2'],
                                  'sse3': ['-msse3'],
                                  'ssse3': ['-mssse3'],
                                  'sse41': ['-msse4.1'],
                                  'sse42': ['-msse4.2'],
                                  'avx': ['-mavx'],
                                  'avx2': ['-mavx2'],
                                  'neon': ['-mfpu=neon'],
                                  }
  
  vs32_instruction_set_args = {'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX
                               'sse': ['/arch:SSE'],
                               'sse2': ['/arch:SSE2'],
                               'sse3': ['/arch:AVX'], # VS leaped from SSE2 directly to AVX.
                               'sse41': ['/arch:AVX'],
                               'sse42': ['/arch:AVX'],
                               'avx': ['/arch:AVX'],
                               'avx2': ['/arch:AVX2'],
                               'neon': None,
                               }
  
  # The 64 bit compiler defaults to /arch:avx.
  vs64_instruction_set_args = {'mmx': ['/arch:AVX'],
                               'sse': ['/arch:AVX'],
                               'sse2': ['/arch:AVX'],
                               'sse3': ['/arch:AVX'],
                               'ssse3': ['/arch:AVX'],
                               'sse41': ['/arch:AVX'],
                               'sse42': ['/arch:AVX'],
                               'avx': ['/arch:AVX'],
                               'avx2': ['/arch:AVX2'],
                               'neon': None,
                               }
  
  gnu_symbol_visibility_args = {'': [],
                                'default': ['-fvisibility=default'],
                                'internal': ['-fvisibility=internal'],
                                'hidden': ['-fvisibility=hidden'],
                                'protected': ['-fvisibility=protected'],
                                'inlineshidden': ['-fvisibility=hidden', '-fvisibility-inlines-hidden'],
                                }
  
  def sanitizer_compile_args(value):
      if value == 'none':
          return []
      args = ['-fsanitize=' + value]
      if 'address' in value: # For -fsanitize=address,undefined
          args.append('-fno-omit-frame-pointer')
      return args
  
  def sanitizer_link_args(value):
      if value == 'none':
          return []
      args = ['-fsanitize=' + value]
      return args
  
  def option_enabled(boptions, options, option):
      try:
          if option not in boptions:
              return False
          return options[option].value
      except KeyError:
          return False
  
  def get_base_compile_args(options, compiler):
      args = []
      # FIXME, gcc/clang specific.
      try:
          if options['b_lto'].value:
              args.append('-flto')
      except KeyError:
          pass
      try:
          args += compiler.get_colorout_args(options['b_colorout'].value)
      except KeyError:
          pass
      try:
          args += sanitizer_compile_args(options['b_sanitize'].value)
      except KeyError:
          pass
      try:
          pgo_val = options['b_pgo'].value
          if pgo_val == 'generate':
              args.append('-fprofile-generate')
          elif pgo_val == 'use':
              args.append('-fprofile-use')
      except KeyError:
          pass
      try:
          if options['b_coverage'].value:
              args += compiler.get_coverage_args()
      except KeyError:
          pass
      try:
          if (options['b_ndebug'].value == 'true' or
                  (options['b_ndebug'].value == 'if-release' and
                   options['buildtype'].value == 'release')):
              args += ['-DNDEBUG']
      except KeyError:
          pass
      # This does not need a try...except
      if option_enabled(compiler.base_options, options, 'b_bitcode'):
          args.append('-fembed-bitcode')
      try:
          crt_val = options['b_vscrt'].value
          buildtype = options['buildtype'].value
          try:
              args += compiler.get_crt_compile_args(crt_val, buildtype)
          except AttributeError:
              pass
      except KeyError:
          pass
      return args
  
  def get_base_link_args(options, linker, is_shared_module):
      args = []
      # FIXME, gcc/clang specific.
      try:
          if options['b_lto'].value:
              args.append('-flto')
      except KeyError:
          pass
      try:
          args += sanitizer_link_args(options['b_sanitize'].value)
      except KeyError:
          pass
      try:
          pgo_val = options['b_pgo'].value
          if pgo_val == 'generate':
              args.append('-fprofile-generate')
          elif pgo_val == 'use':
              args.append('-fprofile-use')
      except KeyError:
          pass
      try:
          if options['b_coverage'].value:
              args += linker.get_coverage_link_args()
      except KeyError:
          pass
      # These do not need a try...except
      if not is_shared_module and option_enabled(linker.base_options, options, 'b_lundef'):
          args.append('-Wl,--no-undefined')
      as_needed = option_enabled(linker.base_options, options, 'b_asneeded')
      bitcode = option_enabled(linker.base_options, options, 'b_bitcode')
      # Shared modules cannot be built with bitcode_bundle because
      # -bitcode_bundle is incompatible with -undefined and -bundle
      if bitcode and not is_shared_module:
          args.append('-Wl,-bitcode_bundle')
      elif as_needed:
          # -Wl,-dead_strip_dylibs is incompatible with bitcode
          args.append(linker.get_asneeded_args())
      try:
          crt_val = options['b_vscrt'].value
          buildtype = options['buildtype'].value
          try:
              args += linker.get_crt_link_args(crt_val, buildtype)
          except AttributeError:
              pass
      except KeyError:
          pass
      return args
  
  class CrossNoRunException(MesonException):
      pass
  
  class RunResult:
      def __init__(self, compiled, returncode=999, stdout='UNDEFINED', stderr='UNDEFINED'):
          self.compiled = compiled
          self.returncode = returncode
          self.stdout = stdout
          self.stderr = stderr
  
  class CompilerArgs(list):
      '''
      Class derived from list() that manages a list of compiler arguments. Should
      be used while constructing compiler arguments from various sources. Can be
      operated with ordinary lists, so this does not need to be used everywhere.
  
      All arguments must be inserted and stored in GCC-style (-lfoo, -Idir, etc)
      and can converted to the native type of each compiler by using the
      .to_native() method to which you must pass an instance of the compiler or
      the compiler class.
  
      New arguments added to this class (either with .append(), .extend(), or +=)
      are added in a way that ensures that they override previous arguments.
      For example:
  
      >>> a = ['-Lfoo', '-lbar']
      >>> a += ['-Lpho', '-lbaz']
      >>> print(a)
      ['-Lpho', '-Lfoo', '-lbar', '-lbaz']
  
      Arguments will also be de-duped if they can be de-duped safely.
  
      Note that because of all this, this class is not commutative and does not
      preserve the order of arguments if it is safe to not. For example:
      >>> ['-Ifoo', '-Ibar'] + ['-Ifez', '-Ibaz', '-Werror']
      ['-Ifez', '-Ibaz', '-Ifoo', '-Ibar', '-Werror']
      >>> ['-Ifez', '-Ibaz', '-Werror'] + ['-Ifoo', '-Ibar']
      ['-Ifoo', '-Ibar', '-Ifez', '-Ibaz', '-Werror']
  
      '''
      # NOTE: currently this class is only for C-like compilers, but it can be
      # extended to other languages easily. Just move the following to the
      # compiler class and initialize when self.compiler is set.
  
      # Arg prefixes that override by prepending instead of appending
      prepend_prefixes = ('-I', '-L')
      # Arg prefixes and args that must be de-duped by returning 2
      dedup2_prefixes = ('-I', '-L', '-D', '-U')
      dedup2_suffixes = ()
      dedup2_args = ()
      # Arg prefixes and args that must be de-duped by returning 1
      dedup1_prefixes = ('-l', '-Wl,-l')
      dedup1_suffixes = ('.lib', '.dll', '.so', '.dylib', '.a')
      # Match a .so of the form path/to/libfoo.so.0.1.0
      # Only UNIX shared libraries require this. Others have a fixed extension.
      dedup1_regex = re.compile(r'([\/\\]|\A)lib.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
      dedup1_args = ('-c', '-S', '-E', '-pipe', '-pthread')
      compiler = None
  
      def _check_args(self, args):
          cargs = []
          if len(args) > 2:
              raise TypeError("CompilerArgs() only accepts at most 2 arguments: "
                              "The compiler, and optionally an initial list")
          elif not args:
              return cargs
          elif len(args) == 1:
              if isinstance(args[0], (Compiler, StaticLinker)):
                  self.compiler = args[0]
              else:
                  raise TypeError("you must pass a Compiler instance as one of "
                                  "the arguments")
          elif len(args) == 2:
              if isinstance(args[0], (Compiler, StaticLinker)):
                  self.compiler = args[0]
                  cargs = args[1]
              elif isinstance(args[1], (Compiler, StaticLinker)):
                  cargs = args[0]
                  self.compiler = args[1]
              else:
                  raise TypeError("you must pass a Compiler instance as one of "
                                  "the two arguments")
          else:
              raise AssertionError('Not reached')
          return cargs
  
      def __init__(self, *args):
          super().__init__(self._check_args(args))
  
      @classmethod
      def _can_dedup(cls, arg):
          '''
          Returns whether the argument can be safely de-duped. This is dependent
          on three things:
  
          a) Whether an argument can be 'overridden' by a later argument.  For
             example, -DFOO defines FOO and -UFOO undefines FOO. In this case, we
             can safely remove the previous occurrence and add a new one. The same
             is true for include paths and library paths with -I and -L. For
             these we return `2`. See `dedup2_prefixes` and `dedup2_args`.
          b) Arguments that once specified cannot be undone, such as `-c` or
             `-pipe`. New instances of these can be completely skipped. For these
             we return `1`. See `dedup1_prefixes` and `dedup1_args`.
          c) Whether it matters where or how many times on the command-line
             a particular argument is present. This can matter for symbol
             resolution in static or shared libraries, so we cannot de-dup or
             reorder them. For these we return `0`. This is the default.
  
          In addition to these, we handle library arguments specially.
          With GNU ld, we surround library arguments with -Wl,--start/end-group
          to recursively search for symbols in the libraries. This is not needed
          with other linkers.
          '''
  
          # A standalone argument must never be deduplicated because it is
          # defined by what comes _after_ it. Thus dedupping this:
          # -D FOO -D BAR
          # would yield either
          # -D FOO BAR
          # or
          # FOO -D BAR
          # both of which are invalid.
          if arg in cls.dedup2_prefixes:
              return 0
          if arg in cls.dedup2_args or \
             arg.startswith(cls.dedup2_prefixes) or \
             arg.endswith(cls.dedup2_suffixes):
              return 2
          if arg in cls.dedup1_args or \
             arg.startswith(cls.dedup1_prefixes) or \
             arg.endswith(cls.dedup1_suffixes) or \
             re.search(cls.dedup1_regex, arg):
              return 1
          return 0
  
      @classmethod
      def _should_prepend(cls, arg):
          if arg.startswith(cls.prepend_prefixes):
              return True
          return False
  
      def to_native(self, copy=False):
          # Check if we need to add --start/end-group for circular dependencies
          # between static libraries, and for recursively searching for symbols
          # needed by static libraries that are provided by object files or
          # shared libraries.
          if copy:
              new = self.copy()
          else:
              new = self
          if get_compiler_uses_gnuld(self.compiler):
              global soregex
              group_start = -1
              group_end = -1
              for i, each in enumerate(new):
                  if not each.startswith(('-Wl,-l', '-l')) and not each.endswith('.a') and \
                     not soregex.match(each):
                      continue
                  group_end = i
                  if group_start < 0:
                      # First occurrence of a library
                      group_start = i
              if group_start >= 0:
                  # Last occurrence of a library
                  new.insert(group_end + 1, '-Wl,--end-group')
                  new.insert(group_start, '-Wl,--start-group')
          return self.compiler.unix_args_to_native(new)
  
      def append_direct(self, arg):
          '''
          Append the specified argument without any reordering or de-dup
          except for absolute paths where the order of include search directories
          is not relevant
          '''
          if os.path.isabs(arg):
              self.append(arg)
          else:
              super().append(arg)
  
      def extend_direct(self, iterable):
          '''
          Extend using the elements in the specified iterable without any
          reordering or de-dup except for absolute paths where the order of
          include search directories is not relevant
          '''
          for elem in iterable:
              self.append_direct(elem)
  
      def __add__(self, args):
          new = CompilerArgs(self, self.compiler)
          new += args
          return new
  
      def __iadd__(self, args):
          '''
          Add two CompilerArgs while taking into account overriding of arguments
          and while preserving the order of arguments as much as possible
          '''
          pre = []
          post = []
          if not isinstance(args, list):
              raise TypeError('can only concatenate list (not "{}") to list'.format(args))
          for arg in args:
              # If the argument can be de-duped, do it either by removing the
              # previous occurrence of it and adding a new one, or not adding the
              # new occurrence.
              dedup = self._can_dedup(arg)
              if dedup == 1:
                  # Argument already exists and adding a new instance is useless
                  if arg in self or arg in pre or arg in post:
                      continue
              if dedup == 2:
                  # Remove all previous occurrences of the arg and add it anew
                  if arg in self:
                      self.remove(arg)
                  if arg in pre:
                      pre.remove(arg)
                  if arg in post:
                      post.remove(arg)
              if self._should_prepend(arg):
                  pre.append(arg)
              else:
                  post.append(arg)
          # Insert at the beginning
          self[:0] = pre
          # Append to the end
          super().__iadd__(post)
          return self
  
      def __radd__(self, args):
          new = CompilerArgs(args, self.compiler)
          new += self
          return new
  
      def __mul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def __imul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def __rmul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def append(self, arg):
          self.__iadd__([arg])
  
      def extend(self, args):
          self.__iadd__(args)
  
  class Compiler:
      # Libraries to ignore in find_library() since they are provided by the
      # compiler or the C library. Currently only used for MSVC.
      ignore_libs = ()
      # Libraries that are internal compiler implementations, and must not be
      # manually searched.
      internal_libs = ()
      # Cache for the result of compiler checks which can be cached
      compiler_check_cache = {}
  
      def __init__(self, exelist, version, **kwargs):
          if isinstance(exelist, str):
              self.exelist = [exelist]
          elif isinstance(exelist, list):
              self.exelist = exelist
          else:
              raise TypeError('Unknown argument to Compiler')
          # In case it's been overridden by a child class already
          if not hasattr(self, 'file_suffixes'):
              self.file_suffixes = lang_suffixes[self.language]
          if not hasattr(self, 'can_compile_suffixes'):
              self.can_compile_suffixes = set(self.file_suffixes)
          self.default_suffix = self.file_suffixes[0]
          self.version = version
          if 'full_version' in kwargs:
              self.full_version = kwargs['full_version']
          else:
              self.full_version = None
          self.base_options = []
  
      def __repr__(self):
          repr_str = "<{0}: v{1} `{2}`>"
          return repr_str.format(self.__class__.__name__, self.version,
                                 ' '.join(self.exelist))
  
      def can_compile(self, src):
          if hasattr(src, 'fname'):
              src = src.fname
          suffix = os.path.splitext(src)[1].lower()
          if suffix and suffix[1:] in self.can_compile_suffixes:
              return True
          return False
  
      def get_id(self):
          return self.id
  
      def get_language(self):
          return self.language
  
      def get_display_language(self):
          return self.language.capitalize()
  
      def get_default_suffix(self):
          return self.default_suffix
  
      def get_define(self, dname, prefix, env, extra_args, dependencies):
          raise EnvironmentException('%s does not support get_define ' % self.get_id())
  
      def compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies):
          raise EnvironmentException('%s does not support compute_int ' % self.get_id())
  
      def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None):
          raise EnvironmentException('%s does not support has_member(s) ' % self.get_id())
  
      def has_type(self, typename, prefix, env, extra_args, dependencies=None):
          raise EnvironmentException('%s does not support has_type ' % self.get_id())
  
      def symbols_have_underscore_prefix(self, env):
          raise EnvironmentException('%s does not support symbols_have_underscore_prefix ' % self.get_id())
  
      def get_exelist(self):
          return self.exelist[:]
  
      def get_builtin_define(self, *args, **kwargs):
          raise EnvironmentException('%s does not support get_builtin_define.' % self.id)
  
      def has_builtin_define(self, *args, **kwargs):
          raise EnvironmentException('%s does not support has_builtin_define.' % self.id)
  
      def get_always_args(self):
          return []
  
      def can_linker_accept_rsp(self):
          """
          Determines whether the linker can accept arguments using the @rsp syntax.
          """
          return mesonlib.is_windows()
  
      def get_linker_always_args(self):
          return []
  
      def gen_import_library_args(self, implibname):
          """
          Used only on Windows for libraries that need an import library.
          This currently means C, C++, Fortran.
          """
          return []
  
      def get_preproc_flags(self):
          if self.get_language() in ('c', 'cpp', 'objc', 'objcpp'):
              return os.environ.get('CPPFLAGS', '')
          return ''
  
      def get_args_from_envvars(self):
          """
          Returns a tuple of (compile_flags, link_flags) for the specified language
          from the inherited environment
          """
          def log_var(var, val):
              if val:
                  mlog.log('Appending {} from environment: {!r}'.format(var, val))
  
          lang = self.get_language()
          compiler_is_linker = False
          if hasattr(self, 'get_linker_exelist'):
              compiler_is_linker = (self.get_exelist() == self.get_linker_exelist())
  
          if lang not in cflags_mapping:
              return [], []
  
          compile_flags = os.environ.get(cflags_mapping[lang], '')
          log_var(cflags_mapping[lang], compile_flags)
          compile_flags = shlex.split(compile_flags)
  
          # Link flags (same for all languages)
          link_flags = os.environ.get('LDFLAGS', '')
          log_var('LDFLAGS', link_flags)
          link_flags = shlex.split(link_flags)
          if compiler_is_linker:
              # When the compiler is used as a wrapper around the linker (such as
              # with GCC and Clang), the compile flags can be needed while linking
              # too. This is also what Autotools does. However, we don't want to do
              # this when the linker is stand-alone such as with MSVC C/C++, etc.
              link_flags = compile_flags + link_flags
  
          # Pre-processor flags (not for fortran or D)
          preproc_flags = self.get_preproc_flags()
          log_var('CPPFLAGS', preproc_flags)
          preproc_flags = shlex.split(preproc_flags)
          compile_flags += preproc_flags
  
          return compile_flags, link_flags
  
      def get_options(self):
          opts = {} # build afresh every time
  
          # Take default values from env variables.
          compile_args, link_args = self.get_args_from_envvars()
          description = 'Extra arguments passed to the {}'.format(self.get_display_language())
          opts.update({
              self.language + '_args': coredata.UserArrayOption(
                  self.language + '_args',
                  description + ' compiler',
                  compile_args, shlex_split=True, user_input=True, allow_dups=True),
              self.language + '_link_args': coredata.UserArrayOption(
                  self.language + '_link_args',
                  description + ' linker',
                  link_args, shlex_split=True, user_input=True, allow_dups=True),
          })
  
          return opts
  
      def get_option_compile_args(self, options):
          return []
  
      def get_option_link_args(self, options):
          return []
  
      def check_header(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language())
  
      def has_header(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language())
  
      def has_header_symbol(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language())
  
      def compiles(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support compile checks.' % self.get_display_language())
  
      def links(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support link checks.' % self.get_display_language())
  
      def run(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support run checks.' % self.get_display_language())
  
      def sizeof(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support sizeof checks.' % self.get_display_language())
  
      def alignment(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support alignment checks.' % self.get_display_language())
  
      def has_function(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support function checks.' % self.get_display_language())
  
      @classmethod
      def unix_args_to_native(cls, args):
          "Always returns a copy that can be independently mutated"
          return args[:]
  
      def find_library(self, *args, **kwargs):
          raise EnvironmentException('Language {} does not support library finding.'.format(self.get_display_language()))
  
      def get_library_dirs(self, *args, **kwargs):
          return ()
  
      def has_multi_arguments(self, args, env):
          raise EnvironmentException(
              'Language {} does not support has_multi_arguments.'.format(
                  self.get_display_language()))
  
      def has_multi_link_arguments(self, args, env):
          raise EnvironmentException(
              'Language {} does not support has_multi_link_arguments.'.format(
                  self.get_display_language()))
  
      def get_cross_extra_flags(self, environment, link):
          extra_flags = []
          if self.is_cross and environment:
              if 'properties' in environment.cross_info.config:
                  props = environment.cross_info.config['properties']
                  lang_args_key = self.language + '_args'
                  extra_flags += props.get(lang_args_key, [])
                  lang_link_args_key = self.language + '_link_args'
                  if link:
                      extra_flags += props.get(lang_link_args_key, [])
          return extra_flags
  
      def _get_compile_output(self, dirname, mode):
          # In pre-processor mode, the output is sent to stdout and discarded
          if mode == 'preprocess':
              return None
          # Extension only matters if running results; '.exe' is
          # guaranteed to be executable on every platform.
          if mode == 'link':
              suffix = 'exe'
          else:
              suffix = 'obj'
          return os.path.join(dirname, 'output.' + suffix)
  
      @contextlib.contextmanager
      def compile(self, code, extra_args=None, mode='link', want_output=False):
          if extra_args is None:
              textra_args = None
              extra_args = []
          else:
              textra_args = tuple(extra_args)
          key = (code, textra_args, mode)
          if not want_output:
              if key in self.compiler_check_cache:
                  p = self.compiler_check_cache[key]
                  mlog.debug('Using cached compile:')
                  mlog.debug('Cached command line: ', ' '.join(p.commands), '\n')
                  mlog.debug('Code:\n', code)
                  mlog.debug('Cached compiler stdout:\n', p.stdo)
                  mlog.debug('Cached compiler stderr:\n', p.stde)
                  yield p
                  return
          try:
              with tempfile.TemporaryDirectory() as tmpdirname:
                  if isinstance(code, str):
                      srcname = os.path.join(tmpdirname,
                                             'testfile.' + self.default_suffix)
                      with open(srcname, 'w') as ofile:
                          ofile.write(code)
                  elif isinstance(code, mesonlib.File):
                      srcname = code.fname
  
                  # Construct the compiler command-line
                  commands = CompilerArgs(self)
                  commands.append(srcname)
                  commands += self.get_always_args()
                  if mode == 'compile':
                      commands += self.get_compile_only_args()
                  # Preprocess mode outputs to stdout, so no output args
                  if mode == 'preprocess':
                      commands += self.get_preprocess_only_args()
                  else:
                      output = self._get_compile_output(tmpdirname, mode)
                      commands += self.get_output_args(output)
                  # extra_args must be last because it could contain '/link' to
                  # pass args to VisualStudio's linker. In that case everything
                  # in the command line after '/link' is given to the linker.
                  commands += extra_args
                  # Generate full command-line with the exelist
                  commands = self.get_exelist() + commands.to_native()
                  mlog.debug('Running compile:')
                  mlog.debug('Working directory: ', tmpdirname)
                  mlog.debug('Command line: ', ' '.join(commands), '\n')
                  mlog.debug('Code:\n', code)
                  os_env = os.environ.copy()
                  os_env['LC_ALL'] = 'C'
                  p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname, env=os_env)
                  mlog.debug('Compiler stdout:\n', p.stdo)
                  mlog.debug('Compiler stderr:\n', p.stde)
                  p.commands = commands
                  p.input_name = srcname
                  if want_output:
                      p.output_name = output
                  else:
                      self.compiler_check_cache[key] = p
                  yield p
          except (PermissionError, OSError):
              # On Windows antivirus programs and the like hold on to files so
              # they can't be deleted. There's not much to do in this case. Also,
              # catch OSError because the directory is then no longer empty.
              pass
  
      def get_colorout_args(self, colortype):
          return []
  
      # Some compilers (msvc) write debug info to a separate file.
      # These args specify where it should be written.
      def get_compile_debugfile_args(self, rel_obj, **kwargs):
          return []
  
      def get_link_debugfile_args(self, rel_obj):
          return []
  
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_std_shared_module_link_args(self, options):
          return self.get_std_shared_lib_link_args()
  
      def get_link_whole_for(self, args):
          if isinstance(args, list) and not args:
              return []
          raise EnvironmentException('Language %s does not support linking whole archives.' % self.get_display_language())
  
      # Compiler arguments needed to enable the given instruction set.
      # May be [] meaning nothing needed or None meaning the given set
      # is not supported.
      def get_instruction_set_args(self, instruction_set):
          return None
  
      def build_osx_rpath_args(self, build_dir, rpath_paths, build_rpath):
          # Ensure that there is enough space for large RPATHs and install_name
          args = ['-Wl,-headerpad_max_install_names']
          if not rpath_paths and not build_rpath:
              return args
          # On OSX, rpaths must be absolute.
          abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths]
          if build_rpath != '':
              abs_rpaths.append(build_rpath)
          # Need to deduplicate abs_rpaths, as rpath_paths and
          # build_rpath are not guaranteed to be disjoint sets
          args += ['-Wl,-rpath,' + rp for rp in OrderedSet(abs_rpaths)]
          return args
  
      def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if not rpath_paths and not install_rpath and not build_rpath:
              return []
          # The rpaths we write must be relative, because otherwise
          # they have different length depending on the build
          # directory. This breaks reproducible builds.
          rel_rpaths = []
          for p in rpath_paths:
              if p == from_dir:
                  relative = '' # relpath errors out in this case
              else:
                  relative = os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir))
              rel_rpaths.append(relative)
          paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths])
          # Build_rpath is used as-is (it is usually absolute).
          if build_rpath != '':
              if paths != '':
                  paths += ':'
              paths += build_rpath
          if len(paths) < len(install_rpath):
              padding = 'X' * (len(install_rpath) - len(paths))
              if not paths:
                  paths = padding
              else:
                  paths = paths + ':' + padding
          args = []
          if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd():
              # This argument instructs the compiler to record the value of
              # ORIGIN in the .dynamic section of the elf. On Linux this is done
              # by default, but is not on dragonfly/openbsd for some reason. Without this
              # $ORIGIN in the runtime path will be undefined and any binaries
              # linked against local libraries will fail to resolve them.
              args.append('-Wl,-z,origin')
          args.append('-Wl,-rpath,' + paths)
          if get_compiler_is_linuxlike(self):
              # Rpaths to use while linking must be absolute. These are not
              # written to the binary. Needed only with GNU ld:
              # https://sourceware.org/bugzilla/show_bug.cgi?id=16936
              # Not needed on Windows or other platforms that don't use RPATH
              # https://github.com/mesonbuild/meson/issues/1897
              lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
  
              # clang expands '-Wl,rpath-link,' to ['-rpath-link'] instead of ['-rpath-link','']
              # This eats the next argument, which happens to be 'ldstdc++', causing link failures.
              # We can dodge this problem by not adding any rpath_paths if the argument is empty.
              if lpaths.strip() != '':
                  args += ['-Wl,-rpath-link,' + lpaths]
          return args
  
      def thread_flags(self, env):
          return []
  
      def openmp_flags(self):
          raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language())
  
      def language_stdlib_only_link_flags(self):
          # The linker flags needed to link the standard library of the current
          # language in. This is needed in cases where you e.g. combine D and C++
          # and both of which need to link their runtime library in or otherwise
          # building fails with undefined symbols.
          return []
  
      def gnu_symbol_visibility_args(self, vistype):
          return []
  
      def get_gui_app_args(self, value):
          return []
  
      def has_func_attribute(self, name, env):
          raise EnvironmentException(
              'Language {} does not support function attributes.'.format(self.get_display_language()))
  
  
  @enum.unique
  class CompilerType(enum.Enum):
      GCC_STANDARD = 0
      GCC_OSX = 1
      GCC_MINGW = 2
      GCC_CYGWIN = 3
  
      CLANG_STANDARD = 10
      CLANG_OSX = 11
      CLANG_MINGW = 12
      # Possibly clang-cl?
  
      ICC_STANDARD = 20
      ICC_OSX = 21
      ICC_WIN = 22
  
      @property
      def is_standard_compiler(self):
          return self.name in ('GCC_STANDARD', 'CLANG_STANDARD', 'ICC_STANDARD')
  
      @property
      def is_osx_compiler(self):
          return self.name in ('GCC_OSX', 'CLANG_OSX', 'ICC_OSX')
  
      @property
      def is_windows_compiler(self):
          return self.name in ('GCC_MINGW', 'GCC_CYGWIN', 'CLANG_MINGW', 'ICC_WIN')
  
  
  # GNU ld cannot be installed on macOS
  # https://github.com/Homebrew/homebrew-core/issues/17794#issuecomment-328174395
  # Hence, we don't need to differentiate between OS and ld
  # for the sake of adding as-needed support
  GNU_LD_AS_NEEDED = '-Wl,--as-needed'
  APPLE_LD_AS_NEEDED = '-Wl,-dead_strip_dylibs'
  
  def get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion):
      install_name = prefix + shlib_name
      if soversion is not None:
          install_name += '.' + soversion
      install_name += '.dylib'
      return '@rpath/' + install_name
  
  def get_gcc_soname_args(compiler_type, prefix, shlib_name, suffix, soversion, darwin_versions, is_shared_module):
      if compiler_type.is_standard_compiler:
          sostr = '' if soversion is None else '.' + soversion
          return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)]
      elif compiler_type.is_windows_compiler:
          # For PE/COFF the soname argument has no effect with GNU LD
          return []
      elif compiler_type.is_osx_compiler:
          if is_shared_module:
              return []
          name = get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion)
          args = ['-install_name', name]
          if darwin_versions:
              args += ['-compatibility_version', darwin_versions[0], '-current_version', darwin_versions[1]]
          return args
      else:
          raise RuntimeError('Not implemented yet.')
  
  def get_compiler_is_linuxlike(compiler):
      compiler_type = getattr(compiler, 'compiler_type', None)
      return compiler_type and compiler_type.is_standard_compiler
  
  def get_compiler_uses_gnuld(c):
      # FIXME: Perhaps we should detect the linker in the environment?
      # FIXME: Assumes that *BSD use GNU ld, but they might start using lld soon
      compiler_type = getattr(c, 'compiler_type', None)
      return compiler_type in (
          CompilerType.GCC_STANDARD,
          CompilerType.GCC_MINGW,
          CompilerType.GCC_CYGWIN,
          CompilerType.CLANG_STANDARD,
          CompilerType.CLANG_MINGW,
          CompilerType.ICC_STANDARD,
          CompilerType.ICC_WIN)
  
  def get_largefile_args(compiler):
      '''
      Enable transparent large-file-support for 32-bit UNIX systems
      '''
      if get_compiler_is_linuxlike(compiler):
          # Enable large-file support unconditionally on all platforms other
          # than macOS and Windows. macOS is now 64-bit-only so it doesn't
          # need anything special, and Windows doesn't have automatic LFS.
          # You must use the 64-bit counterparts explicitly.
          # glibc, musl, and uclibc, and all BSD libcs support this. On Android,
          # support for transparent LFS is available depending on the version of
          # Bionic: https://github.com/android/platform_bionic#32-bit-abi-bugs
          # https://code.google.com/p/android/issues/detail?id=64613
          #
          # If this breaks your code, fix it! It's been 20+ years!
          return ['-D_FILE_OFFSET_BITS=64']
          # We don't enable -D_LARGEFILE64_SOURCE since that enables
          # transitionary features and must be enabled by programs that use
          # those features explicitly.
      return []
  
  # TODO: The result from calling compiler should be cached. So that calling this
  # function multiple times don't add latency.
  def gnulike_default_include_dirs(compiler, lang):
      if lang == 'cpp':
          lang = 'c++'
      env = os.environ.copy()
      env["LC_ALL"] = 'C'
      cmd = compiler + ['-x{}'.format(lang), '-E', '-v', '-']
      p = subprocess.Popen(
          cmd,
          stdin=subprocess.DEVNULL,
          stderr=subprocess.PIPE,
          stdout=subprocess.PIPE,
          env=env
      )
      stderr = p.stderr.read().decode('utf-8', errors='replace')
      parse_state = 0
      paths = []
      for line in stderr.split('\n'):
          if parse_state == 0:
              if line == '#include "..." search starts here:':
                  parse_state = 1
          elif parse_state == 1:
              if line == '#include <...> search starts here:':
                  parse_state = 2
              else:
                  paths.append(line[1:])
          elif parse_state == 2:
              if line == 'End of search list.':
                  break
              else:
                  paths.append(line[1:])
      if len(paths) == 0:
          mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd)))
      return paths
  
  class GnuCompiler:
      # Functionality that is common to all GNU family compilers.
      def __init__(self, compiler_type, defines):
          self.id = 'gcc'
          self.compiler_type = compiler_type
          self.defines = defines or {}
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_colorout', 'b_ndebug', 'b_staticpic']
          if self.compiler_type.is_osx_compiler:
              self.base_options.append('b_bitcode')
          else:
              self.base_options.append('b_lundef')
          self.base_options.append('b_asneeded')
          # All GCC backends can do assembly
          self.can_compile_suffixes.add('s')
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.compiler_type.is_osx_compiler:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_colorout_args(self, colortype):
          if mesonlib.version_compare(self.version, '>=4.9.0'):
              return gnu_color_args[colortype][:]
          return []
  
      def get_warn_args(self, level):
          args = super().get_warn_args(level)
          if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args:
              # -Wpedantic was added in 4.8.0
              # https://gcc.gnu.org/gcc-4.8/changes.html
              args[args.index('-Wpedantic')] = '-pedantic'
          return args
  
      def has_builtin_define(self, define):
          return define in self.defines
  
      def get_builtin_define(self, define):
          if define in self.defines:
              return self.defines[define]
  
      def get_pic_args(self):
          if self.compiler_type in (CompilerType.GCC_CYGWIN, CompilerType.GCC_MINGW, CompilerType.GCC_OSX):
              return [] # On Window and OS X, pic is always on.
          return ['-fPIC']
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_buildtype_linker_args(self, buildtype):
          if self.compiler_type.is_osx_compiler:
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'gch'
  
      def split_shlib_to_parts(self, fname):
          return os.path.dirname(fname), fname
  
      def get_soname_args(self, *args):
          return get_gcc_soname_args(self.compiler_type, *args)
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_link_whole_for(self, args):
          return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive']
  
      def gen_vs_module_defs_args(self, defsfile):
          if not isinstance(defsfile, str):
              raise RuntimeError('Module definitions file should be str')
          # On Windows targets, .def files may be specified on the linker command
          # line like an object file.
          if self.compiler_type in (CompilerType.GCC_CYGWIN, CompilerType.GCC_MINGW):
              return [defsfile]
          # For other targets, discard the .def file.
          return []
  
      def get_gui_app_args(self, value):
          if self.compiler_type in (CompilerType.GCC_CYGWIN, CompilerType.GCC_MINGW) and value:
              return ['-mwindows']
          return []
  
      def get_instruction_set_args(self, instruction_set):
          return gnulike_instruction_set_args.get(instruction_set, None)
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          return ['-fopenmp']
  
      def gnu_symbol_visibility_args(self, vistype):
          return gnu_symbol_visibility_args[vistype]
  
  class ElbrusCompiler(GnuCompiler):
      # Elbrus compiler is nearly like GCC, but does not support
      # PCH, LTO, sanitizers and color output as of version 1.21.x.
      def __init__(self, compiler_type, defines):
          GnuCompiler.__init__(self, compiler_type, defines)
          self.id = 'lcc'
          self.base_options = ['b_pgo', 'b_coverage',
                               'b_ndebug', 'b_staticpic',
                               'b_lundef', 'b_asneeded']
  
      # FIXME: use _build_wrapper to call this so that linker flags from the env
      # get applied
      def get_library_dirs(self, env):
          os_env = os.environ.copy()
          os_env['LC_ALL'] = 'C'
          stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1]
          paths = ()
          for line in stdo.split('\n'):
              if line.startswith('libraries:'):
                  # lcc does not include '=' in --print-search-dirs output.
                  libstr = line.split(' ', 1)[1]
                  paths = (os.path.realpath(p) for p in libstr.split(':'))
                  break
          return paths
  
      def get_program_dirs(self, env):
          os_env = os.environ.copy()
          os_env['LC_ALL'] = 'C'
          stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1]
          paths = ()
          for line in stdo.split('\n'):
              if line.startswith('programs:'):
                  # lcc does not include '=' in --print-search-dirs output.
                  libstr = line.split(' ', 1)[1]
                  paths = (os.path.realpath(p) for p in libstr.split(':'))
                  break
          return paths
  
  class ClangCompiler:
      def __init__(self, compiler_type):
          self.id = 'clang'
          self.compiler_type = compiler_type
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_ndebug', 'b_staticpic', 'b_colorout']
          if self.compiler_type.is_osx_compiler:
              self.base_options.append('b_bitcode')
          else:
              self.base_options.append('b_lundef')
          self.base_options.append('b_asneeded')
          # All Clang backends can do assembly and LLVM IR
          self.can_compile_suffixes.update(['ll', 's'])
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.compiler_type.is_osx_compiler:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_pic_args(self):
          if self.compiler_type in (CompilerType.CLANG_MINGW, CompilerType.CLANG_OSX):
              return [] # On Window and OS X, pic is always on.
          return ['-fPIC']
  
      def get_colorout_args(self, colortype):
          return clang_color_args[colortype][:]
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          if self.compiler_type.is_osx_compiler:
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return clike_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_pch_suffix(self):
          return 'pch'
  
      def get_pch_use_args(self, pch_dir, header):
          # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136
          # This flag is internal to Clang (or at least not documented on the man page)
          # so it might change semantics at any time.
          return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
  
      def get_soname_args(self, *args):
          return get_gcc_soname_args(self.compiler_type, *args)
  
      def has_multi_arguments(self, args, env):
          myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']
          if mesonlib.version_compare(self.version, '>=3.6.0'):
              myargs.append('-Werror=ignored-optimization-argument')
          return super().has_multi_arguments(
              myargs + args,
              env)
  
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          # Starting with XCode 8, we need to pass this to force linker
          # visibility to obey OS X and iOS minimum version targets with
          # -mmacosx-version-min, -miphoneos-version-min, etc.
          # https://github.com/Homebrew/homebrew-core/issues/3727
          if self.compiler_type.is_osx_compiler and version_compare(self.version, '>=8.0'):
              extra_args.append('-Wl,-no_weak_imports')
          return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
      def get_std_shared_module_link_args(self, options):
          if self.compiler_type.is_osx_compiler:
              return ['-bundle', '-Wl,-undefined,dynamic_lookup']
          return ['-shared']
  
      def get_link_whole_for(self, args):
          if self.compiler_type.is_osx_compiler:
              result = []
              for a in args:
                  result += ['-Wl,-force_load', a]
              return result
          return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive']
  
      def get_instruction_set_args(self, instruction_set):
          return gnulike_instruction_set_args.get(instruction_set, None)
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          if version_compare(self.version, '>=3.8.0'):
              return ['-fopenmp']
          elif version_compare(self.version, '>=3.7.0'):
              return ['-fopenmp=libomp']
          else:
              # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency.
              return []
  
      def gnu_symbol_visibility_args(self, vistype):
          return gnu_symbol_visibility_args[vistype]
  
  class ArmclangCompiler:
      def __init__(self):
          if not self.is_cross:
              raise EnvironmentException('armclang supports only cross-compilation.')
          # Check whether 'armlink.exe' is available in path
          self.linker_exe = 'armlink.exe'
          args = '--vsn'
          try:
              p, stdo, stderr = Popen_safe(self.linker_exe, args)
          except OSError as e:
              err_msg = 'Unknown linker\nRunning "{0}" gave \n"{1}"'.format(' '.join([self.linker_exe] + [args]), e)
              raise EnvironmentException(err_msg)
          # Verify the armlink version
          ver_str = re.search('.*Component.*', stdo)
          if ver_str:
              ver_str = ver_str.group(0)
          else:
              EnvironmentException('armlink version string not found')
          # Using the regular expression from environment.search_version,
          # which is used for searching compiler version
          version_regex = '(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)'
          linker_ver = re.search(version_regex, ver_str)
          if linker_ver:
              linker_ver = linker_ver.group(0)
          if not version_compare(self.version, '==' + linker_ver):
              raise EnvironmentException('armlink version does not match with compiler version')
          self.id = 'armclang'
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_ndebug', 'b_staticpic', 'b_colorout']
          # Assembly
          self.can_compile_suffixes.update('s')
  
      def can_linker_accept_rsp(self):
          return False
  
      def get_pic_args(self):
          # PIC support is not enabled by default for ARM,
          # if users want to use it, they need to add the required arguments explicitly
          return []
  
      def get_colorout_args(self, colortype):
          return clang_color_args[colortype][:]
  
      def get_buildtype_args(self, buildtype):
          return armclang_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return arm_buildtype_linker_args[buildtype]
  
      # Override CCompiler.get_std_shared_lib_link_args
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_pch_suffix(self):
          return 'gch'
  
      def get_pch_use_args(self, pch_dir, header):
          # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136
          # This flag is internal to Clang (or at least not documented on the man page)
          # so it might change semantics at any time.
          return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
  
      # Override CCompiler.get_dependency_gen_args
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      # Override CCompiler.build_rpath_args
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return []
  
      def get_linker_exelist(self):
          return [self.linker_exe]
  
  
  # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1
  class IntelCompiler:
      def __init__(self, compiler_type):
          self.id = 'intel'
          self.compiler_type = compiler_type
          self.lang_header = 'none'
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_colorout', 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded']
          # Assembly
          self.can_compile_suffixes.add('s')
  
      def get_pic_args(self):
          return ['-fPIC']
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'pchi'
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-pch', '-pch_dir', os.path.join(pch_dir), '-x',
                  self.lang_header, '-include', header, '-x', 'none']
  
      def get_pch_name(self, header_name):
          return os.path.basename(header_name) + '.' + self.get_pch_suffix()
  
      def split_shlib_to_parts(self, fname):
          return os.path.dirname(fname), fname
  
      def get_soname_args(self, *args):
          return get_gcc_soname_args(self.compiler_type, *args)
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.compiler_type.is_osx_compiler:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_std_shared_lib_link_args(self):
          # FIXME: Don't know how icc works on OSX
          # if self.compiler_type.is_osx_compiler:
          #     return ['-bundle']
          return ['-shared']
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          if version_compare(self.version, '>=15.0.0'):
              return ['-qopenmp']
          else:
              return ['-openmp']
  
      def get_link_whole_for(self, args):
          return GnuCompiler.get_link_whole_for(self, args)
  
  
  class ArmCompiler:
      # Functionality that is common to all ARM family compilers.
      def __init__(self):
          if not self.is_cross:
              raise EnvironmentException('armcc supports only cross-compilation.')
          self.id = 'arm'
          default_warn_args = []
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + [],
                            '3': default_warn_args + []}
          # Assembly
          self.can_compile_suffixes.add('s')
  
      def can_linker_accept_rsp(self):
          return False
  
      def get_pic_args(self):
          # FIXME: Add /ropi, /rwpi, /fpic etc. qualifiers to --apcs
          return []
  
      def get_buildtype_args(self, buildtype):
          return arm_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return arm_buildtype_linker_args[buildtype]
  
      # Override CCompiler.get_always_args
      def get_always_args(self):
          return []
  
      # Override CCompiler.get_dependency_gen_args
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      # Override CCompiler.get_std_shared_lib_link_args
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_pch_use_args(self, pch_dir, header):
          # FIXME: Add required arguments
          # NOTE from armcc user guide:
          # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05
          # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported
          # PCH files."
          return []
  
      def get_pch_suffix(self):
          # NOTE from armcc user guide:
          # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05
          # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported
          # PCH files."
          return 'pch'
  
      def thread_flags(self, env):
          return []
  
      def thread_link_flags(self, env):
          return []
  
      def get_linker_exelist(self):
          args = ['armlink']
          return args
  
      def get_coverage_args(self):
          return []
  
      def get_coverage_link_args(self):
          return []
  
files: mesonbuild/compilers/compilers.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import contextlib, os.path, re, tempfile, shlex
  import subprocess
  
  from ..linkers import StaticLinker
  from .. import coredata
  from .. import mlog
  from .. import mesonlib
  from ..mesonlib import EnvironmentException, MesonException, OrderedSet, version_compare, Popen_safe
  
  """This file contains the data files of all compilers Meson knows
  about. To support a new compiler, add its information below.
  Also add corresponding autodetection code in environment.py."""
  
  header_suffixes = ('h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di')
  obj_suffixes = ('o', 'obj', 'res')
  lib_suffixes = ('a', 'lib', 'dll', 'dylib', 'so')
  # Mapping of language to suffixes of files that should always be in that language
  # This means we can't include .h headers here since they could be C, C++, ObjC, etc.
  lang_suffixes = {
      'c': ('c',),
      'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx'),
      # f90, f95, f03, f08 are for free-form fortran ('f90' recommended)
      # f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended)
      'fortran': ('f90', 'f95', 'f03', 'f08', 'f', 'for', 'ftn', 'fpp'),
      'd': ('d', 'di'),
      'objc': ('m',),
      'objcpp': ('mm',),
      'rust': ('rs',),
      'vala': ('vala', 'vapi', 'gs'),
      'cs': ('cs',),
      'swift': ('swift',),
      'java': ('java',),
  }
  all_languages = lang_suffixes.keys()
  cpp_suffixes = lang_suffixes['cpp'] + ('h',)
  c_suffixes = lang_suffixes['c'] + ('h',)
  # List of languages that by default consume and output libraries following the
  # C ABI; these can generally be used interchangebly
  clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran',)
  # List of languages that can be linked with C code directly by the linker
  # used in build.py:process_compilers() and build.py:get_dynamic_linker()
  # XXX: Add Rust to this?
  clink_langs = ('d',) + clib_langs
  clink_suffixes = ()
  for _l in clink_langs + ('vala',):
      clink_suffixes += lang_suffixes[_l]
  clink_suffixes += ('h', 'll', 's')
  
  soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
  
  # Environment variables that each lang uses.
  cflags_mapping = {'c': 'CFLAGS',
                    'cpp': 'CXXFLAGS',
                    'objc': 'OBJCFLAGS',
                    'objcpp': 'OBJCXXFLAGS',
                    'fortran': 'FFLAGS',
                    'd': 'DFLAGS',
                    'vala': 'VALAFLAGS',
                    'rust': 'RUSTFLAGS'}
  
  # All these are only for C-linkable languages; see `clink_langs` above.
  
  def sort_clink(lang):
      '''
      Sorting function to sort the list of languages according to
      reversed(compilers.clink_langs) and append the unknown langs in the end.
      The purpose is to prefer C over C++ for files that can be compiled by
      both such as assembly, C, etc. Also applies to ObjC, ObjC++, etc.
      '''
      if lang not in clink_langs:
          return 1
      return -clink_langs.index(lang)
  
  def is_header(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1]
      return suffix in header_suffixes
  
  def is_source(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1].lower()
      return suffix in clink_suffixes
  
  def is_assembly(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      return fname.split('.')[-1].lower() == 's'
  
  def is_llvm_ir(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      return fname.split('.')[-1] == 'll'
  
  def is_object(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
      suffix = fname.split('.')[-1]
      return suffix in obj_suffixes
  
  def is_library(fname):
      if hasattr(fname, 'fname'):
          fname = fname.fname
  
      if soregex.match(fname):
          return True
  
      suffix = fname.split('.')[-1]
      return suffix in lib_suffixes
  
  gnulike_buildtype_args = {'plain': [],
                            'debug': [],
                            'debugoptimized': [],
                            'release': [],
                            'minsize': []}
  
  armclang_buildtype_args = {'plain': [],
                             'debug': ['-O0', '-g'],
                             'debugoptimized': ['-O1', '-g'],
                             'release': ['-Os'],
                             'minsize': ['-Oz']}
  
  arm_buildtype_args = {'plain': [],
                        'debug': ['-O0', '--debug'],
                        'debugoptimized': ['-O1', '--debug'],
                        'release': ['-O3', '-Otime'],
                        'minsize': ['-O3', '-Ospace'],
                        }
  
  msvc_buildtype_args = {'plain': [],
                         'debug': ["/ZI", "/Ob0", "/Od", "/RTC1"],
                         'debugoptimized': ["/Zi", "/Ob1"],
                         'release': ["/Ob2", "/Gw"],
                         'minsize': ["/Zi", "/Gw"],
                         }
  
  apple_buildtype_linker_args = {'plain': [],
                                 'debug': [],
                                 'debugoptimized': [],
                                 'release': [],
                                 'minsize': [],
                                 }
  
  gnulike_buildtype_linker_args = {'plain': [],
                                   'debug': [],
                                   'debugoptimized': [],
                                   'release': ['-Wl,-O1'],
                                   'minsize': [],
                                   }
  
  arm_buildtype_linker_args = {'plain': [],
                               'debug': [],
                               'debugoptimized': [],
                               'release': [],
                               'minsize': [],
                               }
  
  msvc_buildtype_linker_args = {'plain': [],
                                'debug': [],
                                'debugoptimized': [],
                                # The otherwise implicit REF and ICF linker
                                # optimisations are disabled by /DEBUG.
                                # REF implies ICF.
                                'release': ['/OPT:REF'],
                                'minsize': ['/INCREMENTAL:NO', '/OPT:REF'],
                                }
  
  java_buildtype_args = {'plain': [],
                         'debug': ['-g'],
                         'debugoptimized': ['-g'],
                         'release': [],
                         'minsize': [],
                         }
  
  rust_buildtype_args = {'plain': [],
                         'debug': [],
                         'debugoptimized': [],
                         'release': [],
                         'minsize': [],
                         }
  
  d_gdc_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-finline-functions'],
                          'release': ['-frelease', '-finline-functions'],
                          'minsize': [],
                          }
  
  d_ldc_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'],
                          'release': ['-release', '-enable-inlining', '-Hkeep-all-bodies'],
                          'minsize': [],
                          }
  
  d_dmd_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': ['-inline'],
                          'release': ['-release', '-inline'],
                          'minsize': [],
                          }
  
  mono_buildtype_args = {'plain': [],
                         'debug': [],
                         'debugoptimized': ['-optimize+'],
                         'release': ['-optimize+'],
                         'minsize': [],
                         }
  
  swift_buildtype_args = {'plain': [],
                          'debug': [],
                          'debugoptimized': [],
                          'release': [],
                          'minsize': [],
                          }
  
  gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32',
                 '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32']
  
  msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib',
                  'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib',
                  'uuid.lib', 'comdlg32.lib', 'advapi32.lib']
  
  gnu_color_args = {'auto': ['-fdiagnostics-color=auto'],
                    'always': ['-fdiagnostics-color=always'],
                    'never': ['-fdiagnostics-color=never'],
                    }
  
  clang_color_args = {'auto': ['-Xclang', '-fcolor-diagnostics'],
                      'always': ['-Xclang', '-fcolor-diagnostics'],
                      'never': ['-Xclang', '-fno-color-diagnostics'],
                      }
  
  clike_optimization_args = {'0': [],
                             'g': [],
                             '1': ['-O1'],
                             '2': ['-O2'],
                             '3': ['-O3'],
                             's': ['-Os'],
                             }
  
  gnu_optimization_args = {'0': [],
                           'g': ['-Og'],
                           '1': ['-O1'],
                           '2': ['-O2'],
                           '3': ['-O3'],
                           's': ['-Os'],
                           }
  
  msvc_optimization_args = {'0': [],
                            'g': ['/O0'],
                            '1': ['/O1'],
                            '2': ['/O2'],
                            '3': ['/O3'],
                            's': ['/O1'], # Implies /Os.
                            }
  
  clike_debug_args = {False: [],
                      True: ['-g']}
  
  msvc_debug_args = {False: [],
                     True: []} # Fixme!
  
  base_options = {'b_pch': coredata.UserBooleanOption('b_pch', 'Use precompiled headers', True),
                  'b_lto': coredata.UserBooleanOption('b_lto', 'Use link time optimization', False),
                  'b_sanitize': coredata.UserComboOption('b_sanitize',
                                                         'Code sanitizer to use',
                                                         ['none', 'address', 'thread', 'undefined', 'memory', 'address,undefined'],
                                                         'none'),
                  'b_lundef': coredata.UserBooleanOption('b_lundef', 'Use -Wl,--no-undefined when linking', True),
                  'b_asneeded': coredata.UserBooleanOption('b_asneeded', 'Use -Wl,--as-needed when linking', True),
                  'b_pgo': coredata.UserComboOption('b_pgo', 'Use profile guided optimization',
                                                    ['off', 'generate', 'use'],
                                                    'off'),
                  'b_coverage': coredata.UserBooleanOption('b_coverage',
                                                           'Enable coverage tracking.',
                                                           False),
                  'b_colorout': coredata.UserComboOption('b_colorout', 'Use colored output',
                                                         ['auto', 'always', 'never'],
                                                         'always'),
                  'b_ndebug': coredata.UserComboOption('b_ndebug', 'Disable asserts',
                                                       ['true', 'false', 'if-release'], 'false'),
                  'b_staticpic': coredata.UserBooleanOption('b_staticpic',
                                                            'Build static libraries as position independent',
                                                            True),
                  'b_bitcode': coredata.UserBooleanOption('b_bitcode',
                                                          'Generate and embed bitcode (only macOS and iOS)',
                                                          False),
                  'b_vscrt': coredata.UserComboOption('b_vscrt', 'VS run-time library type to use.',
                                                      ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype'],
                                                      'from_buildtype'),
                  }
  
  gnulike_instruction_set_args = {'mmx': ['-mmmx'],
                                  'sse': ['-msse'],
                                  'sse2': ['-msse2'],
                                  'sse3': ['-msse3'],
                                  'ssse3': ['-mssse3'],
                                  'sse41': ['-msse4.1'],
                                  'sse42': ['-msse4.2'],
                                  'avx': ['-mavx'],
                                  'avx2': ['-mavx2'],
                                  'neon': ['-mfpu=neon'],
                                  }
  
  vs32_instruction_set_args = {'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX
                               'sse': ['/arch:SSE'],
                               'sse2': ['/arch:SSE2'],
                               'sse3': ['/arch:AVX'], # VS leaped from SSE2 directly to AVX.
                               'sse41': ['/arch:AVX'],
                               'sse42': ['/arch:AVX'],
                               'avx': ['/arch:AVX'],
                               'avx2': ['/arch:AVX2'],
                               'neon': None,
                               }
  
  # The 64 bit compiler defaults to /arch:avx.
  vs64_instruction_set_args = {'mmx': ['/arch:AVX'],
                               'sse': ['/arch:AVX'],
                               'sse2': ['/arch:AVX'],
                               'sse3': ['/arch:AVX'],
                               'ssse3': ['/arch:AVX'],
                               'sse41': ['/arch:AVX'],
                               'sse42': ['/arch:AVX'],
                               'avx': ['/arch:AVX'],
                               'avx2': ['/arch:AVX2'],
                               'neon': None,
                               }
  
  gnu_symbol_visibility_args = {'': [],
                                'default': ['-fvisibility=default'],
                                'internal': ['-fvisibility=internal'],
                                'hidden': ['-fvisibility=hidden'],
                                'protected': ['-fvisibility=protected'],
                                'inlineshidden': ['-fvisibility=hidden', '-fvisibility-inlines-hidden'],
                                }
  
  def sanitizer_compile_args(value):
      if value == 'none':
          return []
      args = ['-fsanitize=' + value]
      if 'address' in value: # For -fsanitize=address,undefined
          args.append('-fno-omit-frame-pointer')
      return args
  
  def sanitizer_link_args(value):
      if value == 'none':
          return []
      args = ['-fsanitize=' + value]
      return args
  
  def option_enabled(boptions, options, option):
      try:
          if option not in boptions:
              return False
          return options[option].value
      except KeyError:
          return False
  
  def get_base_compile_args(options, compiler):
      args = []
      # FIXME, gcc/clang specific.
      try:
          if options['b_lto'].value:
              args.append('-flto')
      except KeyError:
          pass
      try:
          args += compiler.get_colorout_args(options['b_colorout'].value)
      except KeyError:
          pass
      try:
          args += sanitizer_compile_args(options['b_sanitize'].value)
      except KeyError:
          pass
      try:
          pgo_val = options['b_pgo'].value
          if pgo_val == 'generate':
              args.append('-fprofile-generate')
          elif pgo_val == 'use':
              args.append('-fprofile-use')
      except KeyError:
          pass
      try:
          if options['b_coverage'].value:
              args += compiler.get_coverage_args()
      except KeyError:
          pass
      try:
          if (options['b_ndebug'].value == 'true' or
                  (options['b_ndebug'].value == 'if-release' and
                   options['buildtype'].value == 'release')):
              args += ['-DNDEBUG']
      except KeyError:
          pass
      # This does not need a try...except
      if option_enabled(compiler.base_options, options, 'b_bitcode'):
          args.append('-fembed-bitcode')
      try:
          crt_val = options['b_vscrt'].value
          buildtype = options['buildtype'].value
          try:
              args += compiler.get_crt_compile_args(crt_val, buildtype)
          except AttributeError:
              pass
      except KeyError:
          pass
      return args
  
  def get_base_link_args(options, linker, is_shared_module):
      args = []
      # FIXME, gcc/clang specific.
      try:
          if options['b_lto'].value:
              args.append('-flto')
      except KeyError:
          pass
      try:
          args += sanitizer_link_args(options['b_sanitize'].value)
      except KeyError:
          pass
      try:
          pgo_val = options['b_pgo'].value
          if pgo_val == 'generate':
              args.append('-fprofile-generate')
          elif pgo_val == 'use':
              args.append('-fprofile-use')
      except KeyError:
          pass
      try:
          if options['b_coverage'].value:
              args += linker.get_coverage_link_args()
      except KeyError:
          pass
      # These do not need a try...except
      if not is_shared_module and option_enabled(linker.base_options, options, 'b_lundef'):
          args.append('-Wl,--no-undefined')
      as_needed = option_enabled(linker.base_options, options, 'b_asneeded')
      bitcode = option_enabled(linker.base_options, options, 'b_bitcode')
      # Shared modules cannot be built with bitcode_bundle because
      # -bitcode_bundle is incompatible with -undefined and -bundle
      if bitcode and not is_shared_module:
          args.append('-Wl,-bitcode_bundle')
      elif as_needed:
          # -Wl,-dead_strip_dylibs is incompatible with bitcode
          args.append(linker.get_asneeded_args())
      try:
          crt_val = options['b_vscrt'].value
          buildtype = options['buildtype'].value
          try:
              args += linker.get_crt_link_args(crt_val, buildtype)
          except AttributeError:
              pass
      except KeyError:
          pass
      return args
  
  class CrossNoRunException(MesonException):
      pass
  
  class RunResult:
      def __init__(self, compiled, returncode=999, stdout='UNDEFINED', stderr='UNDEFINED'):
          self.compiled = compiled
          self.returncode = returncode
          self.stdout = stdout
          self.stderr = stderr
  
  class CompilerArgs(list):
      '''
      Class derived from list() that manages a list of compiler arguments. Should
      be used while constructing compiler arguments from various sources. Can be
      operated with ordinary lists, so this does not need to be used everywhere.
  
      All arguments must be inserted and stored in GCC-style (-lfoo, -Idir, etc)
      and can converted to the native type of each compiler by using the
      .to_native() method to which you must pass an instance of the compiler or
      the compiler class.
  
      New arguments added to this class (either with .append(), .extend(), or +=)
      are added in a way that ensures that they override previous arguments.
      For example:
  
      >>> a = ['-Lfoo', '-lbar']
      >>> a += ['-Lpho', '-lbaz']
      >>> print(a)
      ['-Lpho', '-Lfoo', '-lbar', '-lbaz']
  
      Arguments will also be de-duped if they can be de-duped safely.
  
      Note that because of all this, this class is not commutative and does not
      preserve the order of arguments if it is safe to not. For example:
      >>> ['-Ifoo', '-Ibar'] + ['-Ifez', '-Ibaz', '-Werror']
      ['-Ifez', '-Ibaz', '-Ifoo', '-Ibar', '-Werror']
      >>> ['-Ifez', '-Ibaz', '-Werror'] + ['-Ifoo', '-Ibar']
      ['-Ifoo', '-Ibar', '-Ifez', '-Ibaz', '-Werror']
  
      '''
      # NOTE: currently this class is only for C-like compilers, but it can be
      # extended to other languages easily. Just move the following to the
      # compiler class and initialize when self.compiler is set.
  
      # Arg prefixes that override by prepending instead of appending
      prepend_prefixes = ('-I', '-L')
      # Arg prefixes and args that must be de-duped by returning 2
      dedup2_prefixes = ('-I', '-L', '-D', '-U')
      dedup2_suffixes = ()
      dedup2_args = ()
      # Arg prefixes and args that must be de-duped by returning 1
      dedup1_prefixes = ('-l', '-Wl,-l')
      dedup1_suffixes = ('.lib', '.dll', '.so', '.dylib', '.a')
      # Match a .so of the form path/to/libfoo.so.0.1.0
      # Only UNIX shared libraries require this. Others have a fixed extension.
      dedup1_regex = re.compile(r'([\/\\]|\A)lib.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
      dedup1_args = ('-c', '-S', '-E', '-pipe', '-pthread')
      compiler = None
  
      def _check_args(self, args):
          cargs = []
          if len(args) > 2:
              raise TypeError("CompilerArgs() only accepts at most 2 arguments: "
                              "The compiler, and optionally an initial list")
          elif not args:
              return cargs
          elif len(args) == 1:
              if isinstance(args[0], (Compiler, StaticLinker)):
                  self.compiler = args[0]
              else:
                  raise TypeError("you must pass a Compiler instance as one of "
                                  "the arguments")
          elif len(args) == 2:
              if isinstance(args[0], (Compiler, StaticLinker)):
                  self.compiler = args[0]
                  cargs = args[1]
              elif isinstance(args[1], (Compiler, StaticLinker)):
                  cargs = args[0]
                  self.compiler = args[1]
              else:
                  raise TypeError("you must pass a Compiler instance as one of "
                                  "the two arguments")
          else:
              raise AssertionError('Not reached')
          return cargs
  
      def __init__(self, *args):
          super().__init__(self._check_args(args))
  
      @classmethod
      def _can_dedup(cls, arg):
          '''
          Returns whether the argument can be safely de-duped. This is dependent
          on three things:
  
          a) Whether an argument can be 'overridden' by a later argument.  For
             example, -DFOO defines FOO and -UFOO undefines FOO. In this case, we
             can safely remove the previous occurrence and add a new one. The same
             is true for include paths and library paths with -I and -L. For
             these we return `2`. See `dedup2_prefixes` and `dedup2_args`.
          b) Arguments that once specified cannot be undone, such as `-c` or
             `-pipe`. New instances of these can be completely skipped. For these
             we return `1`. See `dedup1_prefixes` and `dedup1_args`.
          c) Whether it matters where or how many times on the command-line
             a particular argument is present. This can matter for symbol
             resolution in static or shared libraries, so we cannot de-dup or
             reorder them. For these we return `0`. This is the default.
  
          In addition to these, we handle library arguments specially.
          With GNU ld, we surround library arguments with -Wl,--start/end-group
          to recursively search for symbols in the libraries. This is not needed
          with other linkers.
          '''
  
          # A standalone argument must never be deduplicated because it is
          # defined by what comes _after_ it. Thus dedupping this:
          # -D FOO -D BAR
          # would yield either
          # -D FOO BAR
          # or
          # FOO -D BAR
          # both of which are invalid.
          if arg in cls.dedup2_prefixes:
              return 0
          if arg in cls.dedup2_args or \
             arg.startswith(cls.dedup2_prefixes) or \
             arg.endswith(cls.dedup2_suffixes):
              return 2
          if arg in cls.dedup1_args or \
             arg.startswith(cls.dedup1_prefixes) or \
             arg.endswith(cls.dedup1_suffixes) or \
             re.search(cls.dedup1_regex, arg):
              return 1
          return 0
  
      @classmethod
      def _should_prepend(cls, arg):
          if arg.startswith(cls.prepend_prefixes):
              return True
          return False
  
      def to_native(self, copy=False):
          # Check if we need to add --start/end-group for circular dependencies
          # between static libraries, and for recursively searching for symbols
          # needed by static libraries that are provided by object files or
          # shared libraries.
          if copy:
              new = self.copy()
          else:
              new = self
          if get_compiler_uses_gnuld(self.compiler):
              global soregex
              group_start = -1
              group_end = -1
              for i, each in enumerate(new):
                  if not each.startswith(('-Wl,-l', '-l')) and not each.endswith('.a') and \
                     not soregex.match(each):
                      continue
                  group_end = i
                  if group_start < 0:
                      # First occurrence of a library
                      group_start = i
              if group_start >= 0:
                  # Last occurrence of a library
                  new.insert(group_end + 1, '-Wl,--end-group')
                  new.insert(group_start, '-Wl,--start-group')
          return self.compiler.unix_args_to_native(new)
  
      def append_direct(self, arg):
          '''
          Append the specified argument without any reordering or de-dup
          except for absolute paths where the order of include search directories
          is not relevant
          '''
          if os.path.isabs(arg):
              self.append(arg)
          else:
              super().append(arg)
  
      def extend_direct(self, iterable):
          '''
          Extend using the elements in the specified iterable without any
          reordering or de-dup except for absolute paths where the order of
          include search directories is not relevant
          '''
          for elem in iterable:
              self.append_direct(elem)
  
      def __add__(self, args):
          new = CompilerArgs(self, self.compiler)
          new += args
          return new
  
      def __iadd__(self, args):
          '''
          Add two CompilerArgs while taking into account overriding of arguments
          and while preserving the order of arguments as much as possible
          '''
          pre = []
          post = []
          if not isinstance(args, list):
              raise TypeError('can only concatenate list (not "{}") to list'.format(args))
          for arg in args:
              # If the argument can be de-duped, do it either by removing the
              # previous occurrence of it and adding a new one, or not adding the
              # new occurrence.
              dedup = self._can_dedup(arg)
              if dedup == 1:
                  # Argument already exists and adding a new instance is useless
                  if arg in self or arg in pre or arg in post:
                      continue
              if dedup == 2:
                  # Remove all previous occurrences of the arg and add it anew
                  if arg in self:
                      self.remove(arg)
                  if arg in pre:
                      pre.remove(arg)
                  if arg in post:
                      post.remove(arg)
              if self._should_prepend(arg):
                  pre.append(arg)
              else:
                  post.append(arg)
          # Insert at the beginning
          self[:0] = pre
          # Append to the end
          super().__iadd__(post)
          return self
  
      def __radd__(self, args):
          new = CompilerArgs(args, self.compiler)
          new += self
          return new
  
      def __mul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def __imul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def __rmul__(self, args):
          raise TypeError("can't multiply compiler arguments")
  
      def append(self, arg):
          self.__iadd__([arg])
  
      def extend(self, args):
          self.__iadd__(args)
  
  class Compiler:
      # Libraries to ignore in find_library() since they are provided by the
      # compiler or the C library. Currently only used for MSVC.
      ignore_libs = ()
      # Libraries that are internal compiler implementations, and must not be
      # manually searched.
      internal_libs = ()
      # Cache for the result of compiler checks which can be cached
      compiler_check_cache = {}
  
      def __init__(self, exelist, version, **kwargs):
          if isinstance(exelist, str):
              self.exelist = [exelist]
          elif isinstance(exelist, list):
              self.exelist = exelist
          else:
              raise TypeError('Unknown argument to Compiler')
          # In case it's been overridden by a child class already
          if not hasattr(self, 'file_suffixes'):
              self.file_suffixes = lang_suffixes[self.language]
          if not hasattr(self, 'can_compile_suffixes'):
              self.can_compile_suffixes = set(self.file_suffixes)
          self.default_suffix = self.file_suffixes[0]
          self.version = version
          if 'full_version' in kwargs:
              self.full_version = kwargs['full_version']
          else:
              self.full_version = None
          self.base_options = []
  
      def __repr__(self):
          repr_str = "<{0}: v{1} `{2}`>"
          return repr_str.format(self.__class__.__name__, self.version,
                                 ' '.join(self.exelist))
  
      def can_compile(self, src):
          if hasattr(src, 'fname'):
              src = src.fname
          suffix = os.path.splitext(src)[1].lower()
          if suffix and suffix[1:] in self.can_compile_suffixes:
              return True
          return False
  
      def get_id(self):
          return self.id
  
      def get_language(self):
          return self.language
  
      def get_display_language(self):
          return self.language.capitalize()
  
      def get_default_suffix(self):
          return self.default_suffix
  
      def get_define(self, dname, prefix, env, extra_args, dependencies):
          raise EnvironmentException('%s does not support get_define ' % self.get_id())
  
      def compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies):
          raise EnvironmentException('%s does not support compute_int ' % self.get_id())
  
      def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None):
          raise EnvironmentException('%s does not support has_member(s) ' % self.get_id())
  
      def has_type(self, typename, prefix, env, extra_args, dependencies=None):
          raise EnvironmentException('%s does not support has_type ' % self.get_id())
  
      def symbols_have_underscore_prefix(self, env):
          raise EnvironmentException('%s does not support symbols_have_underscore_prefix ' % self.get_id())
  
      def get_exelist(self):
          return self.exelist[:]
  
      def get_builtin_define(self, *args, **kwargs):
          raise EnvironmentException('%s does not support get_builtin_define.' % self.id)
  
      def has_builtin_define(self, *args, **kwargs):
          raise EnvironmentException('%s does not support has_builtin_define.' % self.id)
  
      def get_always_args(self):
          return []
  
      def can_linker_accept_rsp(self):
          """
          Determines whether the linker can accept arguments using the @rsp syntax.
          """
          return mesonlib.is_windows()
  
      def get_linker_always_args(self):
          return []
  
      def gen_import_library_args(self, implibname):
          """
          Used only on Windows for libraries that need an import library.
          This currently means C, C++, Fortran.
          """
          return []
  
      def get_preproc_flags(self):
          if self.get_language() in ('c', 'cpp', 'objc', 'objcpp'):
              return os.environ.get('CPPFLAGS', '')
          return ''
  
      def get_args_from_envvars(self):
          """
          Returns a tuple of (compile_flags, link_flags) for the specified language
          from the inherited environment
          """
          def log_var(var, val):
              if val:
                  mlog.log('Appending {} from environment: {!r}'.format(var, val))
  
          lang = self.get_language()
          compiler_is_linker = False
          if hasattr(self, 'get_linker_exelist'):
              compiler_is_linker = (self.get_exelist() == self.get_linker_exelist())
  
          if lang not in cflags_mapping:
              return [], []
  
          compile_flags = os.environ.get(cflags_mapping[lang], '')
          log_var(cflags_mapping[lang], compile_flags)
          compile_flags = shlex.split(compile_flags)
  
          # Link flags (same for all languages)
          link_flags = os.environ.get('LDFLAGS', '')
          log_var('LDFLAGS', link_flags)
          link_flags = shlex.split(link_flags)
          if compiler_is_linker:
              # When the compiler is used as a wrapper around the linker (such as
              # with GCC and Clang), the compile flags can be needed while linking
              # too. This is also what Autotools does. However, we don't want to do
              # this when the linker is stand-alone such as with MSVC C/C++, etc.
              link_flags = compile_flags + link_flags
  
          # Pre-processor flags (not for fortran or D)
          preproc_flags = self.get_preproc_flags()
          log_var('CPPFLAGS', preproc_flags)
          preproc_flags = shlex.split(preproc_flags)
          compile_flags += preproc_flags
  
          return compile_flags, link_flags
  
      def get_options(self):
          opts = {} # build afresh every time
  
          # Take default values from env variables.
          compile_args, link_args = self.get_args_from_envvars()
          description = 'Extra arguments passed to the {}'.format(self.get_display_language())
          opts.update({
              self.language + '_args': coredata.UserArrayOption(
                  self.language + '_args',
                  description + ' compiler',
                  compile_args, shlex_split=True, user_input=True, allow_dups=True),
              self.language + '_link_args': coredata.UserArrayOption(
                  self.language + '_link_args',
                  description + ' linker',
                  link_args, shlex_split=True, user_input=True, allow_dups=True),
          })
  
          return opts
  
      def get_option_compile_args(self, options):
          return []
  
      def get_option_link_args(self, options):
          return []
  
      def check_header(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language())
  
      def has_header(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language())
  
      def has_header_symbol(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language())
  
      def compiles(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support compile checks.' % self.get_display_language())
  
      def links(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support link checks.' % self.get_display_language())
  
      def run(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support run checks.' % self.get_display_language())
  
      def sizeof(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support sizeof checks.' % self.get_display_language())
  
      def alignment(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support alignment checks.' % self.get_display_language())
  
      def has_function(self, *args, **kwargs):
          raise EnvironmentException('Language %s does not support function checks.' % self.get_display_language())
  
      @classmethod
      def unix_args_to_native(cls, args):
          "Always returns a copy that can be independently mutated"
          return args[:]
  
      def find_library(self, *args, **kwargs):
          raise EnvironmentException('Language {} does not support library finding.'.format(self.get_display_language()))
  
      def get_library_dirs(self, *args, **kwargs):
          return ()
  
      def has_multi_arguments(self, args, env):
          raise EnvironmentException(
              'Language {} does not support has_multi_arguments.'.format(
                  self.get_display_language()))
  
      def has_multi_link_arguments(self, args, env):
          raise EnvironmentException(
              'Language {} does not support has_multi_link_arguments.'.format(
                  self.get_display_language()))
  
      def get_cross_extra_flags(self, environment, link):
          extra_flags = []
          if self.is_cross and environment:
              if 'properties' in environment.cross_info.config:
                  props = environment.cross_info.config['properties']
                  lang_args_key = self.language + '_args'
                  extra_flags += props.get(lang_args_key, [])
                  lang_link_args_key = self.language + '_link_args'
                  if link:
                      extra_flags += props.get(lang_link_args_key, [])
          return extra_flags
  
      def _get_compile_output(self, dirname, mode):
          # In pre-processor mode, the output is sent to stdout and discarded
          if mode == 'preprocess':
              return None
          # Extension only matters if running results; '.exe' is
          # guaranteed to be executable on every platform.
          if mode == 'link':
              suffix = 'exe'
          else:
              suffix = 'obj'
          return os.path.join(dirname, 'output.' + suffix)
  
      @contextlib.contextmanager
      def compile(self, code, extra_args=None, mode='link', want_output=False):
          if extra_args is None:
              textra_args = None
              extra_args = []
          else:
              textra_args = tuple(extra_args)
          key = (code, textra_args, mode)
          if not want_output:
              if key in self.compiler_check_cache:
                  p = self.compiler_check_cache[key]
                  mlog.debug('Using cached compile:')
                  mlog.debug('Cached command line: ', ' '.join(p.commands), '\n')
                  mlog.debug('Code:\n', code)
                  mlog.debug('Cached compiler stdout:\n', p.stdo)
                  mlog.debug('Cached compiler stderr:\n', p.stde)
                  yield p
                  return
          try:
              with tempfile.TemporaryDirectory() as tmpdirname:
                  if isinstance(code, str):
                      srcname = os.path.join(tmpdirname,
                                             'testfile.' + self.default_suffix)
                      with open(srcname, 'w') as ofile:
                          ofile.write(code)
                  elif isinstance(code, mesonlib.File):
                      srcname = code.fname
  
                  # Construct the compiler command-line
                  commands = CompilerArgs(self)
                  commands.append(srcname)
                  commands += self.get_always_args()
                  if mode == 'compile':
                      commands += self.get_compile_only_args()
                  # Preprocess mode outputs to stdout, so no output args
                  if mode == 'preprocess':
                      commands += self.get_preprocess_only_args()
                  else:
                      output = self._get_compile_output(tmpdirname, mode)
                      commands += self.get_output_args(output)
                  # extra_args must be last because it could contain '/link' to
                  # pass args to VisualStudio's linker. In that case everything
                  # in the command line after '/link' is given to the linker.
                  commands += extra_args
                  # Generate full command-line with the exelist
                  commands = self.get_exelist() + commands.to_native()
                  mlog.debug('Running compile:')
                  mlog.debug('Working directory: ', tmpdirname)
                  mlog.debug('Command line: ', ' '.join(commands), '\n')
                  mlog.debug('Code:\n', code)
                  os_env = os.environ.copy()
                  os_env['LC_ALL'] = 'C'
                  p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname, env=os_env)
                  mlog.debug('Compiler stdout:\n', p.stdo)
                  mlog.debug('Compiler stderr:\n', p.stde)
                  p.commands = commands
                  p.input_name = srcname
                  if want_output:
                      p.output_name = output
                  else:
                      self.compiler_check_cache[key] = p
                  yield p
          except (PermissionError, OSError):
              # On Windows antivirus programs and the like hold on to files so
              # they can't be deleted. There's not much to do in this case. Also,
              # catch OSError because the directory is then no longer empty.
              pass
  
      def get_colorout_args(self, colortype):
          return []
  
      # Some compilers (msvc) write debug info to a separate file.
      # These args specify where it should be written.
      def get_compile_debugfile_args(self, rel_obj, **kwargs):
          return []
  
      def get_link_debugfile_args(self, rel_obj):
          return []
  
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_std_shared_module_link_args(self, options):
          return self.get_std_shared_lib_link_args()
  
      def get_link_whole_for(self, args):
          if isinstance(args, list) and not args:
              return []
          raise EnvironmentException('Language %s does not support linking whole archives.' % self.get_display_language())
  
      # Compiler arguments needed to enable the given instruction set.
      # May be [] meaning nothing needed or None meaning the given set
      # is not supported.
      def get_instruction_set_args(self, instruction_set):
          return None
  
      def build_osx_rpath_args(self, build_dir, rpath_paths, build_rpath):
          # Ensure that there is enough space for large RPATHs and install_name
          args = ['-Wl,-headerpad_max_install_names']
          if not rpath_paths and not build_rpath:
              return args
          # On OSX, rpaths must be absolute.
          abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths]
          if build_rpath != '':
              abs_rpaths.append(build_rpath)
          # Need to deduplicate abs_rpaths, as rpath_paths and
          # build_rpath are not guaranteed to be disjoint sets
          args += ['-Wl,-rpath,' + rp for rp in OrderedSet(abs_rpaths)]
          return args
  
      def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if not rpath_paths and not install_rpath and not build_rpath:
              return []
          # The rpaths we write must be relative, because otherwise
          # they have different length depending on the build
          # directory. This breaks reproducible builds.
          rel_rpaths = []
          for p in rpath_paths:
              if p == from_dir:
                  relative = '' # relpath errors out in this case
              else:
                  relative = os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir))
              rel_rpaths.append(relative)
          paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths])
          # Build_rpath is used as-is (it is usually absolute).
          if build_rpath != '':
              if paths != '':
                  paths += ':'
              paths += build_rpath
          if len(paths) < len(install_rpath):
              padding = 'X' * (len(install_rpath) - len(paths))
              if not paths:
                  paths = padding
              else:
                  paths = paths + ':' + padding
          args = []
          if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd():
              # This argument instructs the compiler to record the value of
              # ORIGIN in the .dynamic section of the elf. On Linux this is done
              # by default, but is not on dragonfly/openbsd for some reason. Without this
              # $ORIGIN in the runtime path will be undefined and any binaries
              # linked against local libraries will fail to resolve them.
              args.append('-Wl,-z,origin')
          args.append('-Wl,-rpath,' + paths)
          if get_compiler_is_linuxlike(self):
              # Rpaths to use while linking must be absolute. These are not
              # written to the binary. Needed only with GNU ld:
              # https://sourceware.org/bugzilla/show_bug.cgi?id=16936
              # Not needed on Windows or other platforms that don't use RPATH
              # https://github.com/mesonbuild/meson/issues/1897
              lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
  
              # clang expands '-Wl,rpath-link,' to ['-rpath-link'] instead of ['-rpath-link','']
              # This eats the next argument, which happens to be 'ldstdc++', causing link failures.
              # We can dodge this problem by not adding any rpath_paths if the argument is empty.
              if lpaths.strip() != '':
                  args += ['-Wl,-rpath-link,' + lpaths]
          return args
  
      def thread_flags(self, env):
          return []
  
      def openmp_flags(self):
          raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language())
  
      def language_stdlib_only_link_flags(self):
          # The linker flags needed to link the standard library of the current
          # language in. This is needed in cases where you e.g. combine D and C++
          # and both of which need to link their runtime library in or otherwise
          # building fails with undefined symbols.
          return []
  
      def gnu_symbol_visibility_args(self, vistype):
          return []
  
      def get_gui_app_args(self, value):
          return []
  
      def has_func_attribute(self, name, env):
          raise EnvironmentException(
              'Language {} does not support function attributes.'.format(self.get_display_language()))
  
  GCC_STANDARD = 0
  GCC_OSX = 1
  GCC_MINGW = 2
  GCC_CYGWIN = 3
  
  CLANG_STANDARD = 0
  CLANG_OSX = 1
  CLANG_WIN = 2
  # Possibly clang-cl?
  
  ICC_STANDARD = 0
  ICC_OSX = 1
  ICC_WIN = 2
  
  # GNU ld cannot be installed on macOS
  # https://github.com/Homebrew/homebrew-core/issues/17794#issuecomment-328174395
  # Hence, we don't need to differentiate between OS and ld
  # for the sake of adding as-needed support
  GNU_LD_AS_NEEDED = '-Wl,--as-needed'
  APPLE_LD_AS_NEEDED = '-Wl,-dead_strip_dylibs'
  
  def get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion):
      install_name = prefix + shlib_name
      if soversion is not None:
          install_name += '.' + soversion
      install_name += '.dylib'
      return '@rpath/' + install_name
  
  def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, soversion, darwin_versions, is_shared_module):
      if gcc_type == GCC_STANDARD:
          sostr = '' if soversion is None else '.' + soversion
          return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)]
      elif gcc_type in (GCC_MINGW, GCC_CYGWIN):
          # For PE/COFF the soname argument has no effect with GNU LD
          return []
      elif gcc_type == GCC_OSX:
          if is_shared_module:
              return []
          name = get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion)
          args = ['-install_name', name]
          if darwin_versions:
              args += ['-compatibility_version', darwin_versions[0], '-current_version', darwin_versions[1]]
          return args
      else:
          raise RuntimeError('Not implemented yet.')
  
  def get_compiler_is_linuxlike(compiler):
      if (getattr(compiler, 'gcc_type', None) == GCC_STANDARD) or \
         (getattr(compiler, 'clang_type', None) == CLANG_STANDARD) or \
         (getattr(compiler, 'icc_type', None) == ICC_STANDARD):
          return True
      return False
  
  def get_compiler_uses_gnuld(c):
      # FIXME: Perhaps we should detect the linker in the environment?
      # FIXME: Assumes that *BSD use GNU ld, but they might start using lld soon
      if (getattr(c, 'gcc_type', None) in (GCC_STANDARD, GCC_MINGW, GCC_CYGWIN)) or \
         (getattr(c, 'clang_type', None) in (CLANG_STANDARD, CLANG_WIN)) or \
         (getattr(c, 'icc_type', None) in (ICC_STANDARD, ICC_WIN)):
          return True
      return False
  
  def get_largefile_args(compiler):
      '''
      Enable transparent large-file-support for 32-bit UNIX systems
      '''
      if get_compiler_is_linuxlike(compiler):
          # Enable large-file support unconditionally on all platforms other
          # than macOS and Windows. macOS is now 64-bit-only so it doesn't
          # need anything special, and Windows doesn't have automatic LFS.
          # You must use the 64-bit counterparts explicitly.
          # glibc, musl, and uclibc, and all BSD libcs support this. On Android,
          # support for transparent LFS is available depending on the version of
          # Bionic: https://github.com/android/platform_bionic#32-bit-abi-bugs
          # https://code.google.com/p/android/issues/detail?id=64613
          #
          # If this breaks your code, fix it! It's been 20+ years!
          return ['-D_FILE_OFFSET_BITS=64']
          # We don't enable -D_LARGEFILE64_SOURCE since that enables
          # transitionary features and must be enabled by programs that use
          # those features explicitly.
      return []
  
  # TODO: The result from calling compiler should be cached. So that calling this
  # function multiple times don't add latency.
  def gnulike_default_include_dirs(compiler, lang):
      if lang == 'cpp':
          lang = 'c++'
      env = os.environ.copy()
      env["LC_ALL"] = 'C'
      cmd = compiler + ['-x{}'.format(lang), '-E', '-v', '-']
      p = subprocess.Popen(
          cmd,
          stdin=subprocess.DEVNULL,
          stderr=subprocess.PIPE,
          stdout=subprocess.PIPE,
          env=env
      )
      stderr = p.stderr.read().decode('utf-8', errors='replace')
      parse_state = 0
      paths = []
      for line in stderr.split('\n'):
          if parse_state == 0:
              if line == '#include "..." search starts here:':
                  parse_state = 1
          elif parse_state == 1:
              if line == '#include <...> search starts here:':
                  parse_state = 2
              else:
                  paths.append(line[1:])
          elif parse_state == 2:
              if line == 'End of search list.':
                  break
              else:
                  paths.append(line[1:])
      if len(paths) == 0:
          mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd)))
      return paths
  
  class GnuCompiler:
      # Functionality that is common to all GNU family compilers.
      def __init__(self, gcc_type, defines):
          self.id = 'gcc'
          self.gcc_type = gcc_type
          self.defines = defines or {}
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_colorout', 'b_ndebug', 'b_staticpic']
          if self.gcc_type == GCC_OSX:
              self.base_options.append('b_bitcode')
          else:
              self.base_options.append('b_lundef')
          self.base_options.append('b_asneeded')
          # All GCC backends can do assembly
          self.can_compile_suffixes.add('s')
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.gcc_type == GCC_OSX:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_colorout_args(self, colortype):
          if mesonlib.version_compare(self.version, '>=4.9.0'):
              return gnu_color_args[colortype][:]
          return []
  
      def get_warn_args(self, level):
          args = super().get_warn_args(level)
          if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args:
              # -Wpedantic was added in 4.8.0
              # https://gcc.gnu.org/gcc-4.8/changes.html
              args[args.index('-Wpedantic')] = '-pedantic'
          return args
  
      def has_builtin_define(self, define):
          return define in self.defines
  
      def get_builtin_define(self, define):
          if define in self.defines:
              return self.defines[define]
  
      def get_pic_args(self):
          if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX):
              return [] # On Window and OS X, pic is always on.
          return ['-fPIC']
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_buildtype_linker_args(self, buildtype):
          if self.gcc_type == GCC_OSX:
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'gch'
  
      def split_shlib_to_parts(self, fname):
          return os.path.dirname(fname), fname
  
      def get_soname_args(self, *args):
          return get_gcc_soname_args(self.gcc_type, *args)
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_link_whole_for(self, args):
          return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive']
  
      def gen_vs_module_defs_args(self, defsfile):
          if not isinstance(defsfile, str):
              raise RuntimeError('Module definitions file should be str')
          # On Windows targets, .def files may be specified on the linker command
          # line like an object file.
          if self.gcc_type in (GCC_CYGWIN, GCC_MINGW):
              return [defsfile]
          # For other targets, discard the .def file.
          return []
  
      def get_gui_app_args(self, value):
          if self.gcc_type in (GCC_CYGWIN, GCC_MINGW) and value:
              return ['-mwindows']
          return []
  
      def get_instruction_set_args(self, instruction_set):
          return gnulike_instruction_set_args.get(instruction_set, None)
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          return ['-fopenmp']
  
      def gnu_symbol_visibility_args(self, vistype):
          return gnu_symbol_visibility_args[vistype]
  
  class ElbrusCompiler(GnuCompiler):
      # Elbrus compiler is nearly like GCC, but does not support
      # PCH, LTO, sanitizers and color output as of version 1.21.x.
      def __init__(self, gcc_type, defines):
          GnuCompiler.__init__(self, gcc_type, defines)
          self.id = 'lcc'
          self.base_options = ['b_pgo', 'b_coverage',
                               'b_ndebug', 'b_staticpic',
                               'b_lundef', 'b_asneeded']
  
      # FIXME: use _build_wrapper to call this so that linker flags from the env
      # get applied
      def get_library_dirs(self, env):
          os_env = os.environ.copy()
          os_env['LC_ALL'] = 'C'
          stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1]
          paths = ()
          for line in stdo.split('\n'):
              if line.startswith('libraries:'):
                  # lcc does not include '=' in --print-search-dirs output.
                  libstr = line.split(' ', 1)[1]
                  paths = (os.path.realpath(p) for p in libstr.split(':'))
                  break
          return paths
  
      def get_program_dirs(self, env):
          os_env = os.environ.copy()
          os_env['LC_ALL'] = 'C'
          stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1]
          paths = ()
          for line in stdo.split('\n'):
              if line.startswith('programs:'):
                  # lcc does not include '=' in --print-search-dirs output.
                  libstr = line.split(' ', 1)[1]
                  paths = (os.path.realpath(p) for p in libstr.split(':'))
                  break
          return paths
  
  class ClangCompiler:
      def __init__(self, clang_type):
          self.id = 'clang'
          self.clang_type = clang_type
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_ndebug', 'b_staticpic', 'b_colorout']
          if self.clang_type == CLANG_OSX:
              self.base_options.append('b_bitcode')
          else:
              self.base_options.append('b_lundef')
          self.base_options.append('b_asneeded')
          # All Clang backends can do assembly and LLVM IR
          self.can_compile_suffixes.update(['ll', 's'])
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.clang_type == CLANG_OSX:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_pic_args(self):
          if self.clang_type in (CLANG_WIN, CLANG_OSX):
              return [] # On Window and OS X, pic is always on.
          return ['-fPIC']
  
      def get_colorout_args(self, colortype):
          return clang_color_args[colortype][:]
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          if self.clang_type == CLANG_OSX:
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return clike_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_pch_suffix(self):
          return 'pch'
  
      def get_pch_use_args(self, pch_dir, header):
          # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136
          # This flag is internal to Clang (or at least not documented on the man page)
          # so it might change semantics at any time.
          return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
  
      def get_soname_args(self, *args):
          if self.clang_type == CLANG_STANDARD:
              gcc_type = GCC_STANDARD
          elif self.clang_type == CLANG_OSX:
              gcc_type = GCC_OSX
          elif self.clang_type == CLANG_WIN:
              gcc_type = GCC_MINGW
          else:
              raise MesonException('Unreachable code when converting clang type to gcc type.')
          return get_gcc_soname_args(gcc_type, *args)
  
      def has_multi_arguments(self, args, env):
          myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']
          if mesonlib.version_compare(self.version, '>=3.6.0'):
              myargs.append('-Werror=ignored-optimization-argument')
          return super().has_multi_arguments(
              myargs + args,
              env)
  
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if extra_args is None:
              extra_args = []
          # Starting with XCode 8, we need to pass this to force linker
          # visibility to obey OS X and iOS minimum version targets with
          # -mmacosx-version-min, -miphoneos-version-min, etc.
          # https://github.com/Homebrew/homebrew-core/issues/3727
          if self.clang_type == CLANG_OSX and version_compare(self.version, '>=8.0'):
              extra_args.append('-Wl,-no_weak_imports')
          return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
      def get_std_shared_module_link_args(self, options):
          if self.clang_type == CLANG_OSX:
              return ['-bundle', '-Wl,-undefined,dynamic_lookup']
          return ['-shared']
  
      def get_link_whole_for(self, args):
          if self.clang_type == CLANG_OSX:
              result = []
              for a in args:
                  result += ['-Wl,-force_load', a]
              return result
          return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive']
  
      def get_instruction_set_args(self, instruction_set):
          return gnulike_instruction_set_args.get(instruction_set, None)
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          if version_compare(self.version, '>=3.8.0'):
              return ['-fopenmp']
          elif version_compare(self.version, '>=3.7.0'):
              return ['-fopenmp=libomp']
          else:
              # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency.
              return []
  
      def gnu_symbol_visibility_args(self, vistype):
          return gnu_symbol_visibility_args[vistype]
  
  class ArmclangCompiler:
      def __init__(self):
          if not self.is_cross:
              raise EnvironmentException('armclang supports only cross-compilation.')
          # Check whether 'armlink.exe' is available in path
          self.linker_exe = 'armlink.exe'
          args = '--vsn'
          try:
              p, stdo, stderr = Popen_safe(self.linker_exe, args)
          except OSError as e:
              err_msg = 'Unknown linker\nRunning "{0}" gave \n"{1}"'.format(' '.join([self.linker_exe] + [args]), e)
              raise EnvironmentException(err_msg)
          # Verify the armlink version
          ver_str = re.search('.*Component.*', stdo)
          if ver_str:
              ver_str = ver_str.group(0)
          else:
              EnvironmentException('armlink version string not found')
          # Using the regular expression from environment.search_version,
          # which is used for searching compiler version
          version_regex = '(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)'
          linker_ver = re.search(version_regex, ver_str)
          if linker_ver:
              linker_ver = linker_ver.group(0)
          if not version_compare(self.version, '==' + linker_ver):
              raise EnvironmentException('armlink version does not match with compiler version')
          self.id = 'armclang'
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_ndebug', 'b_staticpic', 'b_colorout']
          # Assembly
          self.can_compile_suffixes.update('s')
  
      def can_linker_accept_rsp(self):
          return False
  
      def get_pic_args(self):
          # PIC support is not enabled by default for ARM,
          # if users want to use it, they need to add the required arguments explicitly
          return []
  
      def get_colorout_args(self, colortype):
          return clang_color_args[colortype][:]
  
      def get_buildtype_args(self, buildtype):
          return armclang_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return arm_buildtype_linker_args[buildtype]
  
      # Override CCompiler.get_std_shared_lib_link_args
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_pch_suffix(self):
          return 'gch'
  
      def get_pch_use_args(self, pch_dir, header):
          # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136
          # This flag is internal to Clang (or at least not documented on the man page)
          # so it might change semantics at any time.
          return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))]
  
      # Override CCompiler.get_dependency_gen_args
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      # Override CCompiler.build_rpath_args
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return []
  
      def get_linker_exelist(self):
          return [self.linker_exe]
  
  
  # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1
  class IntelCompiler:
      def __init__(self, icc_type):
          self.id = 'intel'
          self.icc_type = icc_type
          self.lang_header = 'none'
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
                               'b_colorout', 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded']
          # Assembly
          self.can_compile_suffixes.add('s')
  
      def get_pic_args(self):
          return ['-fPIC']
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return gnulike_buildtype_linker_args[buildtype]
  
      def get_pch_suffix(self):
          return 'pchi'
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-pch', '-pch_dir', os.path.join(pch_dir), '-x',
                  self.lang_header, '-include', header, '-x', 'none']
  
      def get_pch_name(self, header_name):
          return os.path.basename(header_name) + '.' + self.get_pch_suffix()
  
      def split_shlib_to_parts(self, fname):
          return os.path.dirname(fname), fname
  
      def get_soname_args(self, *args):
          if self.icc_type == ICC_STANDARD:
              gcc_type = GCC_STANDARD
          elif self.icc_type == ICC_OSX:
              gcc_type = GCC_OSX
          elif self.icc_type == ICC_WIN:
              gcc_type = GCC_MINGW
          else:
              raise MesonException('Unreachable code when converting icc type to gcc type.')
          return get_gcc_soname_args(gcc_type, *args)
  
      # TODO: centralise this policy more globally, instead
      # of fragmenting it into GnuCompiler and ClangCompiler
      def get_asneeded_args(self):
          if self.icc_type == CLANG_OSX:
              return APPLE_LD_AS_NEEDED
          else:
              return GNU_LD_AS_NEEDED
  
      def get_std_shared_lib_link_args(self):
          # FIXME: Don't know how icc works on OSX
          # if self.icc_type == ICC_OSX:
          #     return ['-bundle']
          return ['-shared']
  
      def get_default_include_dirs(self):
          return gnulike_default_include_dirs(self.exelist, self.language)
  
      def openmp_flags(self):
          if version_compare(self.version, '>=15.0.0'):
              return ['-qopenmp']
          else:
              return ['-openmp']
  
      def get_link_whole_for(self, args):
          return GnuCompiler.get_link_whole_for(self, args)
  
  
  class ArmCompiler:
      # Functionality that is common to all ARM family compilers.
      def __init__(self):
          if not self.is_cross:
              raise EnvironmentException('armcc supports only cross-compilation.')
          self.id = 'arm'
          default_warn_args = []
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + [],
                            '3': default_warn_args + []}
          # Assembly
          self.can_compile_suffixes.add('s')
  
      def can_linker_accept_rsp(self):
          return False
  
      def get_pic_args(self):
          # FIXME: Add /ropi, /rwpi, /fpic etc. qualifiers to --apcs
          return []
  
      def get_buildtype_args(self, buildtype):
          return arm_buildtype_args[buildtype]
  
      def get_buildtype_linker_args(self, buildtype):
          return arm_buildtype_linker_args[buildtype]
  
      # Override CCompiler.get_always_args
      def get_always_args(self):
          return []
  
      # Override CCompiler.get_dependency_gen_args
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      # Override CCompiler.get_std_shared_lib_link_args
      def get_std_shared_lib_link_args(self):
          return []
  
      def get_pch_use_args(self, pch_dir, header):
          # FIXME: Add required arguments
          # NOTE from armcc user guide:
          # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05
          # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported
          # PCH files."
          return []
  
      def get_pch_suffix(self):
          # NOTE from armcc user guide:
          # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05
          # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported
          # PCH files."
          return 'pch'
  
      def thread_flags(self, env):
          return []
  
      def thread_link_flags(self, env):
          return []
  
      def get_linker_exelist(self):
          args = ['armlink']
          return args
  
      def get_coverage_args(self):
          return []
  
      def get_coverage_link_args(self):
          return []
  
files: mesonbuild/compilers/compilers.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import functools
  import os.path
  
  from .. import coredata
  from .. import mlog
  from ..mesonlib import MesonException, version_compare
  
  from .c import CCompiler, VisualStudioCCompiler
  from .compilers import (
      CompilerType,
      gnu_winlibs,
      msvc_winlibs,
      ClangCompiler,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
      ArmCompiler,
      ArmclangCompiler,
  )
  from .c_function_attributes import CXX_FUNC_ATTRIBUTES
  
  class CPPCompiler(CCompiler):
  
      @classmethod
      def attribute_check_func(cls, name):
          return CXX_FUNC_ATTRIBUTES.get(name, super().attribute_check_func(name))
  
      def __init__(self, exelist, version, is_cross, exe_wrap, **kwargs):
          # If a child ObjCPP class has already set it, don't set it ourselves
          if not hasattr(self, 'language'):
              self.language = 'cpp'
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
  
      def get_display_language(self):
          return 'C++'
  
      def get_no_stdinc_args(self):
          return ['-nostdinc++']
  
      def sanity_check(self, work_dir, environment):
          code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n'
          return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
  
      def get_compiler_check_args(self):
          # -fpermissive allows non-conforming code to compile which is necessary
          # for many C++ checks. Particularly, the has_header_symbol check is
          # too strict without this and always fails.
          return super().get_compiler_check_args() + ['-fpermissive']
  
      def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None):
          # Check if it's a C-like symbol
          if super().has_header_symbol(hname, symbol, prefix, env, extra_args, dependencies):
              return True
          # Check if it's a class or a template
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol}
          t = '''{prefix}
          #include <{header}>
          using {symbol};
          int main () {{ return 0; }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def _test_cpp_std_arg(self, cpp_std_value):
          # Test whether the compiler understands a -std=XY argument
          assert(cpp_std_value.startswith('-std='))
  
          # This test does not use has_multi_arguments() for two reasons:
          # 1. has_multi_arguments() requires an env argument, which the compiler
          #    object does not have at this point.
          # 2. even if it did have an env object, that might contain another more
          #    recent -std= argument, which might lead to a cascaded failure.
          CPP_TEST = 'int i = static_cast<int>(0);'
          with self.compile(code=CPP_TEST, extra_args=[cpp_std_value], mode='compile') as p:
              if p.returncode == 0:
                  mlog.debug('Compiler accepts {}:'.format(cpp_std_value), 'YES')
                  return True
              else:
                  mlog.debug('Compiler accepts {}:'.format(cpp_std_value), 'NO')
                  return False
  
      @functools.lru_cache()
      def _find_best_cpp_std(self, cpp_std):
          # The initial version mapping approach to make falling back
          # from '-std=c++14' to '-std=c++1y' was too brittle. For instance,
          # Apple's Clang uses a different versioning scheme to upstream LLVM,
          # making the whole detection logic awfully brittle. Instead, let's
          # just see if feeding GCC or Clang our '-std=' setting works, and
          # if not, try the fallback argument.
          CPP_FALLBACKS = {
              'c++11': 'c++0x',
              'gnu++11': 'gnu++0x',
              'c++14': 'c++1y',
              'gnu++14': 'gnu++1y',
              'c++17': 'c++1z',
              'gnu++17': 'gnu++1z'
          }
  
          # Currently, remapping is only supported for Clang and GCC
          assert(self.id in frozenset(['clang', 'gcc']))
  
          if cpp_std not in CPP_FALLBACKS:
              # 'c++03' and 'c++98' don't have fallback types
              return '-std=' + cpp_std
  
          for i in (cpp_std, CPP_FALLBACKS[cpp_std]):
              cpp_std_value = '-std=' + i
              if self._test_cpp_std_arg(cpp_std_value):
                  return cpp_std_value
  
          raise MesonException('C++ Compiler does not support -std={}'.format(cpp_std))
  
  
  class ClangCPPCompiler(ClangCompiler, CPPCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ClangCompiler.__init__(self, compiler_type)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a',
                                                            'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append(self._find_best_cpp_std(std.value))
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def language_stdlib_only_link_flags(self):
          return ['-lstdc++']
  
  
  class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmclangCompiler.__init__(self)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17',
                                                            'gnu++98', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class GnuCPPCompiler(GnuCompiler, CPPCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrap, defines, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          GnuCompiler.__init__(self, compiler_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a',
                                                            'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'],
                                                           'none'),
                       'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl',
                                                                  'STL debug mode',
                                                                  False)})
          if self.compiler_type == CompilerType.GCC_MINGW:
              opts.update({
                  'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs', 'Standard Win libraries to link against',
                                                          gnu_winlibs), })
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append(self._find_best_cpp_std(std.value))
          if options['cpp_debugstl'].value:
              args.append('-D_GLIBCXX_DEBUG=1')
          return args
  
      def get_option_link_args(self, options):
          if self.compiler_type == CompilerType.GCC_MINGW:
              return options['cpp_winlibs'].value[:]
          return []
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-fpch-preprocess', '-include', os.path.basename(header)]
  
      def language_stdlib_only_link_flags(self):
          return ['-lstdc++']
  
  
  class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuCPPCompiler.__init__(self, exelist, version, compiler_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, compiler_type, defines)
  
      # It does not support c++/gnu++ 17 and 1z, but still does support 0x, 1y, and gnu++98.
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts['cpp_std'] = coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                     ['none', 'c++98', 'c++03', 'c++0x', 'c++11', 'c++14', 'c++1y',
                                                      'gnu++98', 'gnu++03', 'gnu++0x', 'gnu++11', 'gnu++14', 'gnu++1y'],
                                                     'none')
          return opts
  
      # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error.
      # So we should explicitly fail at this case.
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if funcname == 'lchmod':
              return False
          else:
              return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
  
  class IntelCPPCompiler(IntelCompiler, CPPCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrap, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          IntelCompiler.__init__(self, compiler_type)
          self.lang_header = 'c++-header'
          default_warn_args = ['-Wall', '-w3', '-diag-disable:remark',
                               '-Wpch-messages', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          c_stds = []
          g_stds = ['gnu++98']
          if version_compare(self.version, '>=15.0.0'):
              c_stds += ['c++11', 'c++14']
              g_stds += ['gnu++11']
          if version_compare(self.version, '>=16.0.0'):
              c_stds += ['c++17']
          if version_compare(self.version, '>=17.0.0'):
              g_stds += ['gnu++14']
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none'] + c_stds + g_stds,
                                                           'none'),
                       'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl',
                                                                  'STL debug mode',
                                                                  False)})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          if options['cpp_debugstl'].value:
              args.append('-D_GLIBCXX_DEBUG=1')
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap, is_64):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
          VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap, is_64)
          self.base_options = ['b_pch', 'b_vscrt'] # FIXME add lto, pgo and the like
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_eh': coredata.UserComboOption('cpp_eh',
                                                          'C++ exception handling type.',
                                                          ['none', 'a', 's', 'sc'],
                                                          'sc'),
                       'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs',
                                                               'Windows libs to link against.',
                                                               msvc_winlibs)})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_eh']
          if std.value != 'none':
              args.append('/EH' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return options['cpp_winlibs'].value[:]
  
      def get_compiler_check_args(self):
          # Visual Studio C++ compiler doesn't support -fpermissive,
          # so just use the plain C args.
          return VisualStudioCCompiler.get_compiler_check_args(self)
  
  
  class ArmCPPCompiler(ArmCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          ArmCompiler.__init__(self)
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++03', 'c++11'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value == 'c++11':
              args.append('--cpp11')
          elif std.value == 'c++03':
              args.append('--cpp')
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def get_compiler_check_args(self):
          return []
  
files: mesonbuild/compilers/cpp.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import functools
  import os.path
  
  from .. import coredata
  from .. import mlog
  from ..mesonlib import MesonException, version_compare
  
  from .c import CCompiler, VisualStudioCCompiler
  from .compilers import (
      GCC_MINGW,
      gnu_winlibs,
      msvc_winlibs,
      ClangCompiler,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
      ArmCompiler,
      ArmclangCompiler,
  )
  from .c_function_attributes import CXX_FUNC_ATTRIBUTES
  
  class CPPCompiler(CCompiler):
  
      @classmethod
      def attribute_check_func(cls, name):
          return CXX_FUNC_ATTRIBUTES.get(name, super().attribute_check_func(name))
  
      def __init__(self, exelist, version, is_cross, exe_wrap, **kwargs):
          # If a child ObjCPP class has already set it, don't set it ourselves
          if not hasattr(self, 'language'):
              self.language = 'cpp'
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
  
      def get_display_language(self):
          return 'C++'
  
      def get_no_stdinc_args(self):
          return ['-nostdinc++']
  
      def sanity_check(self, work_dir, environment):
          code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n'
          return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
  
      def get_compiler_check_args(self):
          # -fpermissive allows non-conforming code to compile which is necessary
          # for many C++ checks. Particularly, the has_header_symbol check is
          # too strict without this and always fails.
          return super().get_compiler_check_args() + ['-fpermissive']
  
      def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None):
          # Check if it's a C-like symbol
          if super().has_header_symbol(hname, symbol, prefix, env, extra_args, dependencies):
              return True
          # Check if it's a class or a template
          if extra_args is None:
              extra_args = []
          fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol}
          t = '''{prefix}
          #include <{header}>
          using {symbol};
          int main () {{ return 0; }}'''
          return self.compiles(t.format(**fargs), env, extra_args, dependencies)
  
      def _test_cpp_std_arg(self, cpp_std_value):
          # Test whether the compiler understands a -std=XY argument
          assert(cpp_std_value.startswith('-std='))
  
          # This test does not use has_multi_arguments() for two reasons:
          # 1. has_multi_arguments() requires an env argument, which the compiler
          #    object does not have at this point.
          # 2. even if it did have an env object, that might contain another more
          #    recent -std= argument, which might lead to a cascaded failure.
          CPP_TEST = 'int i = static_cast<int>(0);'
          with self.compile(code=CPP_TEST, extra_args=[cpp_std_value], mode='compile') as p:
              if p.returncode == 0:
                  mlog.debug('Compiler accepts {}:'.format(cpp_std_value), 'YES')
                  return True
              else:
                  mlog.debug('Compiler accepts {}:'.format(cpp_std_value), 'NO')
                  return False
  
      @functools.lru_cache()
      def _find_best_cpp_std(self, cpp_std):
          # The initial version mapping approach to make falling back
          # from '-std=c++14' to '-std=c++1y' was too brittle. For instance,
          # Apple's Clang uses a different versioning scheme to upstream LLVM,
          # making the whole detection logic awfully brittle. Instead, let's
          # just see if feeding GCC or Clang our '-std=' setting works, and
          # if not, try the fallback argument.
          CPP_FALLBACKS = {
              'c++11': 'c++0x',
              'gnu++11': 'gnu++0x',
              'c++14': 'c++1y',
              'gnu++14': 'gnu++1y',
              'c++17': 'c++1z',
              'gnu++17': 'gnu++1z'
          }
  
          # Currently, remapping is only supported for Clang and GCC
          assert(self.id in frozenset(['clang', 'gcc']))
  
          if cpp_std not in CPP_FALLBACKS:
              # 'c++03' and 'c++98' don't have fallback types
              return '-std=' + cpp_std
  
          for i in (cpp_std, CPP_FALLBACKS[cpp_std]):
              cpp_std_value = '-std=' + i
              if self._test_cpp_std_arg(cpp_std_value):
                  return cpp_std_value
  
          raise MesonException('C++ Compiler does not support -std={}'.format(cpp_std))
  
  
  class ClangCPPCompiler(ClangCompiler, CPPCompiler):
      def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ClangCompiler.__init__(self, cltype)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a',
                                                            'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append(self._find_best_cpp_std(std.value))
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def language_stdlib_only_link_flags(self):
          return ['-lstdc++']
  
  
  class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          ArmclangCompiler.__init__(self)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17',
                                                            'gnu++98', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class GnuCPPCompiler(GnuCompiler, CPPCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrap, defines, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          GnuCompiler.__init__(self, gcc_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a',
                                                            'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'],
                                                           'none'),
                       'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl',
                                                                  'STL debug mode',
                                                                  False)})
          if self.gcc_type == GCC_MINGW:
              opts.update({
                  'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs', 'Standard Win libraries to link against',
                                                          gnu_winlibs), })
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append(self._find_best_cpp_std(std.value))
          if options['cpp_debugstl'].value:
              args.append('-D_GLIBCXX_DEBUG=1')
          return args
  
      def get_option_link_args(self, options):
          if self.gcc_type == GCC_MINGW:
              return options['cpp_winlibs'].value[:]
          return []
  
      def get_pch_use_args(self, pch_dir, header):
          return ['-fpch-preprocess', '-include', os.path.basename(header)]
  
      def language_stdlib_only_link_flags(self):
          return ['-lstdc++']
  
  
  class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuCPPCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, gcc_type, defines)
  
      # It does not support c++/gnu++ 17 and 1z, but still does support 0x, 1y, and gnu++98.
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts['cpp_std'] = coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                     ['none', 'c++98', 'c++03', 'c++0x', 'c++11', 'c++14', 'c++1y',
                                                      'gnu++98', 'gnu++03', 'gnu++0x', 'gnu++11', 'gnu++14', 'gnu++1y'],
                                                     'none')
          return opts
  
      # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error.
      # So we should explicitly fail at this case.
      def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None):
          if funcname == 'lchmod':
              return False
          else:
              return super().has_function(funcname, prefix, env, extra_args, dependencies)
  
  
  class IntelCPPCompiler(IntelCompiler, CPPCompiler):
      def __init__(self, exelist, version, icc_type, is_cross, exe_wrap, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          IntelCompiler.__init__(self, icc_type)
          self.lang_header = 'c++-header'
          default_warn_args = ['-Wall', '-w3', '-diag-disable:remark',
                               '-Wpch-messages', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          c_stds = []
          g_stds = ['gnu++98']
          if version_compare(self.version, '>=15.0.0'):
              c_stds += ['c++11', 'c++14']
              g_stds += ['gnu++11']
          if version_compare(self.version, '>=16.0.0'):
              c_stds += ['c++17']
          if version_compare(self.version, '>=17.0.0'):
              g_stds += ['gnu++14']
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none'] + c_stds + g_stds,
                                                           'none'),
                       'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl',
                                                                  'STL debug mode',
                                                                  False)})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value != 'none':
              args.append('-std=' + std.value)
          if options['cpp_debugstl'].value:
              args.append('-D_GLIBCXX_DEBUG=1')
          return args
  
      def get_option_link_args(self, options):
          return []
  
  
  class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap, is_64):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
          VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap, is_64)
          self.base_options = ['b_pch', 'b_vscrt'] # FIXME add lto, pgo and the like
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_eh': coredata.UserComboOption('cpp_eh',
                                                          'C++ exception handling type.',
                                                          ['none', 'a', 's', 'sc'],
                                                          'sc'),
                       'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs',
                                                               'Windows libs to link against.',
                                                               msvc_winlibs)})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_eh']
          if std.value != 'none':
              args.append('/EH' + std.value)
          return args
  
      def get_option_link_args(self, options):
          return options['cpp_winlibs'].value[:]
  
      def get_compiler_check_args(self):
          # Visual Studio C++ compiler doesn't support -fpermissive,
          # so just use the plain C args.
          return VisualStudioCCompiler.get_compiler_check_args(self)
  
  
  class ArmCPPCompiler(ArmCompiler, CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap=None, **kwargs):
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs)
          ArmCompiler.__init__(self)
  
      def get_options(self):
          opts = CPPCompiler.get_options(self)
          opts.update({'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use',
                                                           ['none', 'c++03', 'c++11'],
                                                           'none')})
          return opts
  
      def get_option_compile_args(self, options):
          args = []
          std = options['cpp_std']
          if std.value == 'c++11':
              args.append('--cpp11')
          elif std.value == 'c++03':
              args.append('--cpp')
          return args
  
      def get_option_link_args(self, options):
          return []
  
      def get_compiler_check_args(self):
          return []
  
files: mesonbuild/compilers/cpp.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException, version_compare, is_windows, is_osx
  
  from .compilers import (
      CompilerType,
      d_dmd_buildtype_args,
      d_gdc_buildtype_args,
      d_ldc_buildtype_args,
      get_gcc_soname_args,
      gnu_color_args,
      gnu_optimization_args,
      clike_debug_args,
      Compiler,
      CompilerArgs,
  )
  
  d_feature_args = {'gcc':  {'unittest': '-funittest',
                             'version': '-fversion',
                             'import_dir': '-J'
                             },
                    'llvm': {'unittest': '-unittest',
                             'version': '-d-version',
                             'import_dir': '-J'
                             },
                    'dmd':  {'unittest': '-unittest',
                             'version': '-version',
                             'import_dir': '-J'
                             }
                    }
  
  ldc_optimization_args = {'0': [],
                           'g': [],
                           '1': ['-O1'],
                           '2': ['-O2'],
                           '3': ['-O3'],
                           's': ['-Os'],
                           }
  
  dmd_optimization_args = {'0': [],
                           'g': [],
                           '1': ['-O'],
                           '2': ['-O'],
                           '3': ['-O'],
                           's': ['-O'],
                           }
  
  class DCompiler(Compiler):
      mscrt_args = {
          'none': ['-mscrtlib='],
          'md': ['-mscrtlib=msvcrt'],
          'mdd': ['-mscrtlib=msvcrtd'],
          'mt': ['-mscrtlib=libcmt'],
          'mtd': ['-mscrtlib=libcmtd'],
      }
  
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          self.language = 'd'
          super().__init__(exelist, version, **kwargs)
          self.id = 'unknown'
          self.is_cross = is_cross
          self.is_64 = is_64
  
      def sanity_check(self, work_dir, environment):
          source_name = os.path.join(work_dir, 'sanity.d')
          output_name = os.path.join(work_dir, 'dtest')
          with open(source_name, 'w') as ofile:
              ofile.write('''void main() { }''')
          pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self.get_target_arch_args() + [source_name], cwd=work_dir)
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string())
          if subprocess.call(output_name) != 0:
              raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string())
  
      def needs_static_linker(self):
          return True
  
      def name_string(self):
          return ' '.join(self.exelist)
  
      def get_exelist(self):
          return self.exelist
  
      def get_linker_exelist(self):
          return self.exelist[:]
  
      def get_output_args(self, target):
          return ['-of=' + target]
  
      def get_linker_output_args(self, target):
          return ['-of=' + target]
  
      def get_include_args(self, path, is_system):
          return ['-I=' + path]
  
      def get_warn_args(self, level):
          return ['-wi']
  
      def get_werror_args(self):
          return ['-w']
  
      def get_dependency_gen_args(self, outtarget, outfile):
          # DMD and LDC does not currently return Makefile-compatible dependency info.
          return []
  
      def get_linker_search_args(self, dirname):
          # -L is recognized as "add this to the search path" by the linker,
          # while the compiler recognizes it as "pass to linker".
          return ['-Wl,-L' + dirname]
  
      def get_coverage_args(self):
          return ['-cov']
  
      def get_preprocess_only_args(self):
          return ['-E']
  
      def get_compile_only_args(self):
          return ['-c']
  
      def depfile_for_object(self, objfile):
          return objfile + '.' + self.get_depfile_suffix()
  
      def get_depfile_suffix(self):
          return 'deps'
  
      def get_pic_args(self):
          if is_windows():
              return []
          return ['-fPIC']
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_soname_args(self, *args):
          # FIXME: Make this work for cross-compiling
          if is_windows():
              return []
          elif is_osx():
              soname_args = get_gcc_soname_args(CompilerType.GCC_OSX, *args)
              if soname_args:
                  return ['-Wl,' + ','.join(soname_args)]
              return []
  
          return get_gcc_soname_args(CompilerType.GCC_STANDARD, *args)
  
      def get_feature_args(self, kwargs, build_to_src):
          res = []
          if 'unittest' in kwargs:
              unittest = kwargs.pop('unittest')
              unittest_arg = d_feature_args[self.id]['unittest']
              if not unittest_arg:
                  raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
              if unittest:
                  res.append(unittest_arg)
  
          if 'versions' in kwargs:
              versions = kwargs.pop('versions')
              if not isinstance(versions, list):
                  versions = [versions]
  
              version_arg = d_feature_args[self.id]['version']
              if not version_arg:
                  raise EnvironmentException('D compiler %s does not support the "feature versions" feature.' % self.name_string())
              for v in versions:
                  res.append('{0}={1}'.format(version_arg, v))
  
          if 'import_dirs' in kwargs:
              import_dirs = kwargs.pop('import_dirs')
              if not isinstance(import_dirs, list):
                  import_dirs = [import_dirs]
  
              import_dir_arg = d_feature_args[self.id]['import_dir']
              if not import_dir_arg:
                  raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
              for idir_obj in import_dirs:
                  basedir = idir_obj.get_curdir()
                  for idir in idir_obj.get_incdirs():
                      # Avoid superfluous '/.' at the end of paths when d is '.'
                      if idir not in ('', '.'):
                          expdir = os.path.join(basedir, idir)
                      else:
                          expdir = basedir
                      srctreedir = os.path.join(build_to_src, expdir)
                      res.append('{0}{1}'.format(import_dir_arg, srctreedir))
  
          if kwargs:
              raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
  
          return res
  
      def get_buildtype_linker_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args()
          return []
  
      def get_std_exe_link_args(self):
          return []
  
      def gen_import_library_args(self, implibname):
          return ['-Wl,--out-implib=' + implibname]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if is_windows():
              return []
  
          # This method is to be used by LDC and DMD.
          # GDC can deal with the verbatim flags.
          if not rpath_paths and not install_rpath:
              return []
          paths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
          if build_rpath != '':
              paths += ':' + build_rpath
          if len(paths) < len(install_rpath):
              padding = 'X' * (len(install_rpath) - len(paths))
              if not paths:
                  paths = padding
              else:
                  paths = paths + ':' + padding
          return ['-Wl,-rpath,{}'.format(paths)]
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          if extra_args is None:
              extra_args = []
          elif isinstance(extra_args, str):
              extra_args = [extra_args]
          if dependencies is None:
              dependencies = []
          elif not isinstance(dependencies, list):
              dependencies = [dependencies]
          # Collect compiler arguments
          args = CompilerArgs(self)
          for d in dependencies:
              # Add compile flags needed by dependencies
              args += d.get_compile_args()
              if mode == 'link':
                  # Add link flags needed to find dependencies
                  args += d.get_link_args()
  
          if mode == 'compile':
              # Add DFLAGS from the env
              args += env.coredata.get_external_args(self.language)
          elif mode == 'link':
              # Add LDFLAGS from the env
              args += env.coredata.get_external_link_args(self.language)
          # extra_args must override all other arguments, so we add them last
          args += extra_args
          return args
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          args = self._get_compiler_check_args(env, extra_args, dependencies, mode)
  
          with self.compile(code, args, mode) as p:
              return p.returncode == 0
  
      def has_multi_arguments(self, args, env):
          return self.compiles('int i;\n', env, extra_args=args)
  
      def get_target_arch_args(self):
          # LDC2 on Windows targets to current OS architecture, but
          # it should follow the target specified by the MSVC toolchain.
          if is_windows():
              if self.is_64:
                  return ['-m64']
              return ['-m32']
          return []
  
      @classmethod
      def translate_args_to_nongnu(cls, args):
          dcargs = []
          # Translate common arguments to flags the LDC/DMD compilers
          # can understand.
          # The flags might have been added by pkg-config files,
          # and are therefore out of the user's control.
          for arg in args:
              # Translate OS specific arguments first.
              osargs = []
              if is_windows():
                  osargs = cls.translate_arg_to_windows(arg)
              elif is_osx():
                  osargs = cls.translate_arg_to_osx(arg)
              if osargs:
                  dcargs.extend(osargs)
                  continue
  
              # Translate common D arguments here.
              if arg == '-pthread':
                  continue
              if arg.startswith('-Wl,'):
                  # Translate linker arguments here.
                  linkargs = arg[arg.index(',') + 1:].split(',')
                  for la in linkargs:
                      dcargs.append('-L=' + la.strip())
                  continue
              elif arg.startswith('-link-defaultlib') or arg.startswith('-linker'):
                  # these are special arguments to the LDC linker call,
                  # arguments like "-link-defaultlib-shared" do *not*
                  # denote a library to be linked, but change the default
                  # Phobos/DRuntime linking behavior, while "-linker" sets the
                  # default linker.
                  dcargs.append(arg)
                  continue
              elif arg.startswith('-l'):
                  # translate library link flag
                  dcargs.append('-L=' + arg)
                  continue
              elif arg.startswith('-L'):
                  # we need to handle cases where -L is set by e.g. a pkg-config
                  # setting to select a linker search path. We can however not
                  # unconditionally prefix '-L' with '-L' because the user might
                  # have set this flag too to do what it is intended to for this
                  # compiler (pass flag through to the linker)
                  # Hence, we guess here whether the flag was intended to pass
                  # a linker search path.
  
                  # Make sure static library files are passed properly to the linker.
                  if arg.endswith('.a') or arg.endswith('.lib'):
                      if arg.startswith('-L='):
                          farg = arg[3:]
                      else:
                          farg = arg[2:]
                      if len(farg) > 0 and not farg.startswith('-'):
                          dcargs.append('-L=' + farg)
                          continue
  
                  dcargs.append('-L=' + arg)
                  continue
  
              dcargs.append(arg)
  
          return dcargs
  
      @classmethod
      def translate_arg_to_windows(cls, arg):
          args = []
          if arg.startswith('-Wl,'):
              # Translate linker arguments here.
              linkargs = arg[arg.index(',') + 1:].split(',')
              for la in linkargs:
                  if la.startswith('--out-implib='):
                      # Import library name
                      args.append('-L=/IMPLIB:' + la[13:].strip())
          elif arg.startswith('-mscrtlib='):
              args.append(arg)
              mscrtlib = arg[10:].lower()
              if cls is LLVMDCompiler:
                  # Default crt libraries for LDC2 must be excluded for other
                  # selected crt options.
                  if mscrtlib != 'libcmt':
                      args.append('-L=/NODEFAULTLIB:libcmt')
                      args.append('-L=/NODEFAULTLIB:libvcruntime')
  
                  # Fixes missing definitions for printf-functions in VS2017
                  if mscrtlib.startswith('msvcrt'):
                      args.append('-L=/DEFAULTLIB:legacy_stdio_definitions.lib')
  
          return args
  
      @classmethod
      def translate_arg_to_osx(cls, arg):
          args = []
          if arg.startswith('-install_name'):
              args.append('-L=' + arg)
          return args
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_crt_args(self, crt_val, buildtype):
          if not is_windows():
              return []
  
          if crt_val in self.mscrt_args:
              return self.mscrt_args[crt_val]
          assert(crt_val == 'from_buildtype')
  
          # Match what build type flags used to do.
          if buildtype == 'plain':
              return []
          elif buildtype == 'debug':
              return self.mscrt_args['mdd']
          elif buildtype == 'debugoptimized':
              return self.mscrt_args['md']
          elif buildtype == 'release':
              return self.mscrt_args['md']
          elif buildtype == 'minsize':
              return self.mscrt_args['md']
          else:
              assert(buildtype == 'custom')
              raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
  
      def get_crt_compile_args(self, crt_val, buildtype):
          return []
  
      def get_crt_link_args(self, crt_val, buildtype):
          return []
  
  class GnuDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'gcc'
          default_warn_args = ['-Wall', '-Wdeprecated']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
          self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt']
  
          self._has_color_support = version_compare(self.version, '>=4.9')
          # dependencies were implemented before, but broken - support was fixed in GCC 7.1+
          # (and some backported versions)
          self._has_deps_support = version_compare(self.version, '>=7.1')
  
      def get_colorout_args(self, colortype):
          if self._has_color_support:
              return gnu_color_args[colortype][:]
          return []
  
      def get_dependency_gen_args(self, outtarget, outfile):
          if not self._has_deps_support:
              return []
          return ['-MD', '-MQ', outtarget, '-MF', outfile]
  
      def get_output_args(self, target):
          return ['-o', target]
  
      def get_linker_output_args(self, target):
          return ['-o', target]
  
      def get_include_args(self, path, is_system):
          return ['-I' + path]
  
      def get_warn_args(self, level):
          return self.warn_args[level]
  
      def get_werror_args(self):
          return ['-Werror']
  
      def get_linker_search_args(self, dirname):
          return ['-L' + dirname]
  
      def get_coverage_args(self):
          return []
  
      def get_buildtype_args(self, buildtype):
          return d_gdc_buildtype_args[buildtype]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
  class LLVMDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'llvm'
          self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt']
  
      def get_colorout_args(self, colortype):
          if colortype == 'always':
              return ['-enable-color']
          return []
  
      def get_warn_args(self, level):
          if level == '2' or level == '3':
              return ['-wi', '-dw']
          else:
              return ['-wi']
  
      def get_buildtype_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args() + d_ldc_buildtype_args[buildtype]
          return d_ldc_buildtype_args[buildtype]
  
      def get_pic_args(self):
          return ['-relocation-model=pic']
  
      def get_crt_link_args(self, crt_val, buildtype):
          return self.get_crt_args(crt_val, buildtype)
  
      @classmethod
      def unix_args_to_native(cls, args):
          return cls.translate_args_to_nongnu(args)
  
      def get_optimization_args(self, optimization_level):
          return ldc_optimization_args[optimization_level]
  
  
  class DmdDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'dmd'
          self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt']
          self.is_msvc = 'VCINSTALLDIR' in os.environ
  
      def get_colorout_args(self, colortype):
          if colortype == 'always':
              return ['-color=on']
          return []
  
      def get_buildtype_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args() + d_dmd_buildtype_args[buildtype]
          return d_dmd_buildtype_args[buildtype]
  
      def get_std_exe_link_args(self):
          if is_windows():
              # DMD links against D runtime only when main symbol is found,
              # so these needs to be inserted when linking static D libraries.
              if self.is_64:
                  return ['phobos64.lib']
              elif self.is_msvc:
                  return ['phobos32mscoff.lib']
              return ['phobos.lib']
          return []
  
      def get_std_shared_lib_link_args(self):
          return ['-shared', '-defaultlib=libphobos2.so']
  
      def get_target_arch_args(self):
          # DMD32 and DMD64 on 64-bit Windows defaults to 32-bit (OMF).
          # Force the target to 64-bit in order to stay consistent
          # across the different platforms.
          if is_windows():
              if self.is_64:
                  return ['-m64']
              elif self.is_msvc:
                  return ['-m32mscoff']
              return ['-m32']
          return []
  
      def get_crt_compile_args(self, crt_val, buildtype):
          return self.get_crt_args(crt_val, buildtype)
  
      @classmethod
      def unix_args_to_native(cls, args):
          return cls.translate_args_to_nongnu(args)
  
      def get_optimization_args(self, optimization_level):
          return dmd_optimization_args[optimization_level]
  
files: mesonbuild/compilers/d.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException, version_compare, is_windows, is_osx
  
  from .compilers import (
      GCC_STANDARD,
      GCC_OSX,
      d_dmd_buildtype_args,
      d_gdc_buildtype_args,
      d_ldc_buildtype_args,
      get_gcc_soname_args,
      gnu_color_args,
      gnu_optimization_args,
      clike_debug_args,
      Compiler,
      CompilerArgs,
  )
  
  d_feature_args = {'gcc':  {'unittest': '-funittest',
                             'version': '-fversion',
                             'import_dir': '-J'
                             },
                    'llvm': {'unittest': '-unittest',
                             'version': '-d-version',
                             'import_dir': '-J'
                             },
                    'dmd':  {'unittest': '-unittest',
                             'version': '-version',
                             'import_dir': '-J'
                             }
                    }
  
  ldc_optimization_args = {'0': [],
                           'g': [],
                           '1': ['-O1'],
                           '2': ['-O2'],
                           '3': ['-O3'],
                           's': ['-Os'],
                           }
  
  dmd_optimization_args = {'0': [],
                           'g': [],
                           '1': ['-O'],
                           '2': ['-O'],
                           '3': ['-O'],
                           's': ['-O'],
                           }
  
  class DCompiler(Compiler):
      mscrt_args = {
          'none': ['-mscrtlib='],
          'md': ['-mscrtlib=msvcrt'],
          'mdd': ['-mscrtlib=msvcrtd'],
          'mt': ['-mscrtlib=libcmt'],
          'mtd': ['-mscrtlib=libcmtd'],
      }
  
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          self.language = 'd'
          super().__init__(exelist, version, **kwargs)
          self.id = 'unknown'
          self.is_cross = is_cross
          self.is_64 = is_64
  
      def sanity_check(self, work_dir, environment):
          source_name = os.path.join(work_dir, 'sanity.d')
          output_name = os.path.join(work_dir, 'dtest')
          with open(source_name, 'w') as ofile:
              ofile.write('''void main() { }''')
          pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self.get_target_arch_args() + [source_name], cwd=work_dir)
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string())
          if subprocess.call(output_name) != 0:
              raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string())
  
      def needs_static_linker(self):
          return True
  
      def name_string(self):
          return ' '.join(self.exelist)
  
      def get_exelist(self):
          return self.exelist
  
      def get_linker_exelist(self):
          return self.exelist[:]
  
      def get_output_args(self, target):
          return ['-of=' + target]
  
      def get_linker_output_args(self, target):
          return ['-of=' + target]
  
      def get_include_args(self, path, is_system):
          return ['-I=' + path]
  
      def get_warn_args(self, level):
          return ['-wi']
  
      def get_werror_args(self):
          return ['-w']
  
      def get_dependency_gen_args(self, outtarget, outfile):
          # DMD and LDC does not currently return Makefile-compatible dependency info.
          return []
  
      def get_linker_search_args(self, dirname):
          # -L is recognized as "add this to the search path" by the linker,
          # while the compiler recognizes it as "pass to linker".
          return ['-Wl,-L' + dirname]
  
      def get_coverage_args(self):
          return ['-cov']
  
      def get_preprocess_only_args(self):
          return ['-E']
  
      def get_compile_only_args(self):
          return ['-c']
  
      def depfile_for_object(self, objfile):
          return objfile + '.' + self.get_depfile_suffix()
  
      def get_depfile_suffix(self):
          return 'deps'
  
      def get_pic_args(self):
          if is_windows():
              return []
          return ['-fPIC']
  
      def get_std_shared_lib_link_args(self):
          return ['-shared']
  
      def get_soname_args(self, *args):
          # FIXME: Make this work for cross-compiling
          if is_windows():
              return []
          elif is_osx():
              soname_args = get_gcc_soname_args(GCC_OSX, *args)
              if soname_args:
                  return ['-Wl,' + ','.join(soname_args)]
              return []
  
          return get_gcc_soname_args(GCC_STANDARD, *args)
  
      def get_feature_args(self, kwargs, build_to_src):
          res = []
          if 'unittest' in kwargs:
              unittest = kwargs.pop('unittest')
              unittest_arg = d_feature_args[self.id]['unittest']
              if not unittest_arg:
                  raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
              if unittest:
                  res.append(unittest_arg)
  
          if 'versions' in kwargs:
              versions = kwargs.pop('versions')
              if not isinstance(versions, list):
                  versions = [versions]
  
              version_arg = d_feature_args[self.id]['version']
              if not version_arg:
                  raise EnvironmentException('D compiler %s does not support the "feature versions" feature.' % self.name_string())
              for v in versions:
                  res.append('{0}={1}'.format(version_arg, v))
  
          if 'import_dirs' in kwargs:
              import_dirs = kwargs.pop('import_dirs')
              if not isinstance(import_dirs, list):
                  import_dirs = [import_dirs]
  
              import_dir_arg = d_feature_args[self.id]['import_dir']
              if not import_dir_arg:
                  raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
              for idir_obj in import_dirs:
                  basedir = idir_obj.get_curdir()
                  for idir in idir_obj.get_incdirs():
                      # Avoid superfluous '/.' at the end of paths when d is '.'
                      if idir not in ('', '.'):
                          expdir = os.path.join(basedir, idir)
                      else:
                          expdir = basedir
                      srctreedir = os.path.join(build_to_src, expdir)
                      res.append('{0}{1}'.format(import_dir_arg, srctreedir))
  
          if kwargs:
              raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
  
          return res
  
      def get_buildtype_linker_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args()
          return []
  
      def get_std_exe_link_args(self):
          return []
  
      def gen_import_library_args(self, implibname):
          return ['-Wl,--out-implib=' + implibname]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          if is_windows():
              return []
  
          # This method is to be used by LDC and DMD.
          # GDC can deal with the verbatim flags.
          if not rpath_paths and not install_rpath:
              return []
          paths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
          if build_rpath != '':
              paths += ':' + build_rpath
          if len(paths) < len(install_rpath):
              padding = 'X' * (len(install_rpath) - len(paths))
              if not paths:
                  paths = padding
              else:
                  paths = paths + ':' + padding
          return ['-Wl,-rpath,{}'.format(paths)]
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          if extra_args is None:
              extra_args = []
          elif isinstance(extra_args, str):
              extra_args = [extra_args]
          if dependencies is None:
              dependencies = []
          elif not isinstance(dependencies, list):
              dependencies = [dependencies]
          # Collect compiler arguments
          args = CompilerArgs(self)
          for d in dependencies:
              # Add compile flags needed by dependencies
              args += d.get_compile_args()
              if mode == 'link':
                  # Add link flags needed to find dependencies
                  args += d.get_link_args()
  
          if mode == 'compile':
              # Add DFLAGS from the env
              args += env.coredata.get_external_args(self.language)
          elif mode == 'link':
              # Add LDFLAGS from the env
              args += env.coredata.get_external_link_args(self.language)
          # extra_args must override all other arguments, so we add them last
          args += extra_args
          return args
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          args = self._get_compiler_check_args(env, extra_args, dependencies, mode)
  
          with self.compile(code, args, mode) as p:
              return p.returncode == 0
  
      def has_multi_arguments(self, args, env):
          return self.compiles('int i;\n', env, extra_args=args)
  
      def get_target_arch_args(self):
          # LDC2 on Windows targets to current OS architecture, but
          # it should follow the target specified by the MSVC toolchain.
          if is_windows():
              if self.is_64:
                  return ['-m64']
              return ['-m32']
          return []
  
      @classmethod
      def translate_args_to_nongnu(cls, args):
          dcargs = []
          # Translate common arguments to flags the LDC/DMD compilers
          # can understand.
          # The flags might have been added by pkg-config files,
          # and are therefore out of the user's control.
          for arg in args:
              # Translate OS specific arguments first.
              osargs = []
              if is_windows():
                  osargs = cls.translate_arg_to_windows(arg)
              elif is_osx():
                  osargs = cls.translate_arg_to_osx(arg)
              if osargs:
                  dcargs.extend(osargs)
                  continue
  
              # Translate common D arguments here.
              if arg == '-pthread':
                  continue
              if arg.startswith('-Wl,'):
                  # Translate linker arguments here.
                  linkargs = arg[arg.index(',') + 1:].split(',')
                  for la in linkargs:
                      dcargs.append('-L=' + la.strip())
                  continue
              elif arg.startswith('-link-defaultlib') or arg.startswith('-linker'):
                  # these are special arguments to the LDC linker call,
                  # arguments like "-link-defaultlib-shared" do *not*
                  # denote a library to be linked, but change the default
                  # Phobos/DRuntime linking behavior, while "-linker" sets the
                  # default linker.
                  dcargs.append(arg)
                  continue
              elif arg.startswith('-l'):
                  # translate library link flag
                  dcargs.append('-L=' + arg)
                  continue
              elif arg.startswith('-L'):
                  # we need to handle cases where -L is set by e.g. a pkg-config
                  # setting to select a linker search path. We can however not
                  # unconditionally prefix '-L' with '-L' because the user might
                  # have set this flag too to do what it is intended to for this
                  # compiler (pass flag through to the linker)
                  # Hence, we guess here whether the flag was intended to pass
                  # a linker search path.
  
                  # Make sure static library files are passed properly to the linker.
                  if arg.endswith('.a') or arg.endswith('.lib'):
                      if arg.startswith('-L='):
                          farg = arg[3:]
                      else:
                          farg = arg[2:]
                      if len(farg) > 0 and not farg.startswith('-'):
                          dcargs.append('-L=' + farg)
                          continue
  
                  dcargs.append('-L=' + arg)
                  continue
  
              dcargs.append(arg)
  
          return dcargs
  
      @classmethod
      def translate_arg_to_windows(cls, arg):
          args = []
          if arg.startswith('-Wl,'):
              # Translate linker arguments here.
              linkargs = arg[arg.index(',') + 1:].split(',')
              for la in linkargs:
                  if la.startswith('--out-implib='):
                      # Import library name
                      args.append('-L=/IMPLIB:' + la[13:].strip())
          elif arg.startswith('-mscrtlib='):
              args.append(arg)
              mscrtlib = arg[10:].lower()
              if cls is LLVMDCompiler:
                  # Default crt libraries for LDC2 must be excluded for other
                  # selected crt options.
                  if mscrtlib != 'libcmt':
                      args.append('-L=/NODEFAULTLIB:libcmt')
                      args.append('-L=/NODEFAULTLIB:libvcruntime')
  
                  # Fixes missing definitions for printf-functions in VS2017
                  if mscrtlib.startswith('msvcrt'):
                      args.append('-L=/DEFAULTLIB:legacy_stdio_definitions.lib')
  
          return args
  
      @classmethod
      def translate_arg_to_osx(cls, arg):
          args = []
          if arg.startswith('-install_name'):
              args.append('-L=' + arg)
          return args
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_crt_args(self, crt_val, buildtype):
          if not is_windows():
              return []
  
          if crt_val in self.mscrt_args:
              return self.mscrt_args[crt_val]
          assert(crt_val == 'from_buildtype')
  
          # Match what build type flags used to do.
          if buildtype == 'plain':
              return []
          elif buildtype == 'debug':
              return self.mscrt_args['mdd']
          elif buildtype == 'debugoptimized':
              return self.mscrt_args['md']
          elif buildtype == 'release':
              return self.mscrt_args['md']
          elif buildtype == 'minsize':
              return self.mscrt_args['md']
          else:
              assert(buildtype == 'custom')
              raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
  
      def get_crt_compile_args(self, crt_val, buildtype):
          return []
  
      def get_crt_link_args(self, crt_val, buildtype):
          return []
  
  class GnuDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'gcc'
          default_warn_args = ['-Wall', '-Wdeprecated']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
          self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt']
  
          self._has_color_support = version_compare(self.version, '>=4.9')
          # dependencies were implemented before, but broken - support was fixed in GCC 7.1+
          # (and some backported versions)
          self._has_deps_support = version_compare(self.version, '>=7.1')
  
      def get_colorout_args(self, colortype):
          if self._has_color_support:
              return gnu_color_args[colortype][:]
          return []
  
      def get_dependency_gen_args(self, outtarget, outfile):
          if not self._has_deps_support:
              return []
          return ['-MD', '-MQ', outtarget, '-MF', outfile]
  
      def get_output_args(self, target):
          return ['-o', target]
  
      def get_linker_output_args(self, target):
          return ['-o', target]
  
      def get_include_args(self, path, is_system):
          return ['-I' + path]
  
      def get_warn_args(self, level):
          return self.warn_args[level]
  
      def get_werror_args(self):
          return ['-Werror']
  
      def get_linker_search_args(self, dirname):
          return ['-L' + dirname]
  
      def get_coverage_args(self):
          return []
  
      def get_buildtype_args(self, buildtype):
          return d_gdc_buildtype_args[buildtype]
  
      def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
          return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
  class LLVMDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'llvm'
          self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt']
  
      def get_colorout_args(self, colortype):
          if colortype == 'always':
              return ['-enable-color']
          return []
  
      def get_warn_args(self, level):
          if level == '2' or level == '3':
              return ['-wi', '-dw']
          else:
              return ['-wi']
  
      def get_buildtype_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args() + d_ldc_buildtype_args[buildtype]
          return d_ldc_buildtype_args[buildtype]
  
      def get_pic_args(self):
          return ['-relocation-model=pic']
  
      def get_crt_link_args(self, crt_val, buildtype):
          return self.get_crt_args(crt_val, buildtype)
  
      @classmethod
      def unix_args_to_native(cls, args):
          return cls.translate_args_to_nongnu(args)
  
      def get_optimization_args(self, optimization_level):
          return ldc_optimization_args[optimization_level]
  
  
  class DmdDCompiler(DCompiler):
      def __init__(self, exelist, version, is_cross, is_64, **kwargs):
          DCompiler.__init__(self, exelist, version, is_cross, is_64, **kwargs)
          self.id = 'dmd'
          self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt']
          self.is_msvc = 'VCINSTALLDIR' in os.environ
  
      def get_colorout_args(self, colortype):
          if colortype == 'always':
              return ['-color=on']
          return []
  
      def get_buildtype_args(self, buildtype):
          if buildtype != 'plain':
              return self.get_target_arch_args() + d_dmd_buildtype_args[buildtype]
          return d_dmd_buildtype_args[buildtype]
  
      def get_std_exe_link_args(self):
          if is_windows():
              # DMD links against D runtime only when main symbol is found,
              # so these needs to be inserted when linking static D libraries.
              if self.is_64:
                  return ['phobos64.lib']
              elif self.is_msvc:
                  return ['phobos32mscoff.lib']
              return ['phobos.lib']
          return []
  
      def get_std_shared_lib_link_args(self):
          return ['-shared', '-defaultlib=libphobos2.so']
  
      def get_target_arch_args(self):
          # DMD32 and DMD64 on 64-bit Windows defaults to 32-bit (OMF).
          # Force the target to 64-bit in order to stay consistent
          # across the different platforms.
          if is_windows():
              if self.is_64:
                  return ['-m64']
              elif self.is_msvc:
                  return ['-m32mscoff']
              return ['-m32']
          return []
  
      def get_crt_compile_args(self, crt_val, buildtype):
          return self.get_crt_args(crt_val, buildtype)
  
      @classmethod
      def unix_args_to_native(cls, args):
          return cls.translate_args_to_nongnu(args)
  
      def get_optimization_args(self, optimization_level):
          return dmd_optimization_args[optimization_level]
  
files: mesonbuild/compilers/d.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 .c import CCompiler
  from .compilers import (
      CompilerType,
      apple_buildtype_linker_args,
      gnulike_buildtype_args,
      gnulike_buildtype_linker_args,
      gnu_optimization_args,
      clike_debug_args,
      Compiler,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
  )
  
  from mesonbuild.mesonlib import EnvironmentException, is_osx
  import subprocess, os
  
  class FortranCompiler(Compiler):
      library_dirs_cache = CCompiler.library_dirs_cache
      program_dirs_cache = CCompiler.library_dirs_cache
      find_library_cache = CCompiler.library_dirs_cache
  
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          self.language = 'fortran'
          Compiler.__init__(self, exelist, version, **kwargs)
          cc = CCompiler(exelist, version, is_cross, exe_wrapper, **kwargs)
          self.id = 'unknown'
          self.is_cross = cc.is_cross
          self.exe_wrapper = cc.exe_wrapper
  
      def get_display_language(self):
          return 'Fortran'
  
      def needs_static_linker(self):
          return CCompiler.needs_static_linker(self)
  
      def get_always_args(self):
          return CCompiler.get_always_args(self)
  
      def get_linker_debug_crt_args(self):
          return CCompiler.get_linker_debug_crt_args(self)
  
      def get_no_stdinc_args(self):
          return CCompiler.get_no_stdinc_args(self)
  
      def get_no_stdlib_link_args(self):
          return CCompiler.get_no_stdlib_link_args(self)
  
      def get_warn_args(self, level):
          return CCompiler.get_warn_args(self, level)
  
      def get_no_warn_args(self):
          return CCompiler.get_no_warn_args(self)
  
      def get_soname_args(self, *args):
          return CCompiler.get_soname_args(self, *args)
  
      def sanity_check(self, work_dir, environment):
          source_name = os.path.join(work_dir, 'sanitycheckf.f90')
          binary_name = os.path.join(work_dir, 'sanitycheckf')
          with open(source_name, 'w') as ofile:
              ofile.write('''program prog
       print *, "Fortran compilation is working."
  end program prog
  ''')
          extra_flags = self.get_cross_extra_flags(environment, link=True)
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              if self.exe_wrapper is None:
                  # Can't check if the binaries run so we have to assume they do
                  return
              cmdlist = self.exe_wrapper + [binary_name]
          else:
              cmdlist = [binary_name]
          pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string())
  
      def get_std_warn_args(self, level):
          return FortranCompiler.std_warn_args
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_buildtype_linker_args(self, buildtype):
          if is_osx():
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def split_shlib_to_parts(self, fname):
          return CCompiler.split_shlib_to_parts(self, fname)
  
      def build_rpath_args(self, *args):
          return CCompiler.build_rpath_args(self, *args)
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      def depfile_for_object(self, objfile):
          return CCompiler.depfile_for_object(self, objfile)
  
      def get_depfile_suffix(self):
          return CCompiler.get_depfile_suffix(self)
  
      def get_exelist(self):
          return CCompiler.get_exelist(self)
  
      def get_linker_exelist(self):
          return CCompiler.get_linker_exelist(self)
  
      def get_preprocess_only_args(self):
          return ['-cpp'] + CCompiler.get_preprocess_only_args(self)
  
      def get_compile_only_args(self):
          return CCompiler.get_compile_only_args(self)
  
      def get_no_optimization_args(self):
          return CCompiler.get_no_optimization_args(self)
  
      def get_compiler_check_args(self):
          return CCompiler.get_compiler_check_args(self)
  
      def get_allow_undefined_link_args(self):
          return CCompiler.get_allow_undefined_link_args(self)
  
      def get_output_args(self, target):
          return CCompiler.get_output_args(self, target)
  
      def get_linker_output_args(self, outputname):
          return CCompiler.get_linker_output_args(self, outputname)
  
      def get_coverage_args(self):
          return CCompiler.get_coverage_args(self)
  
      def get_coverage_link_args(self):
          return CCompiler.get_coverage_link_args(self)
  
      def get_werror_args(self):
          return CCompiler.get_werror_args(self)
  
      def get_std_exe_link_args(self):
          return CCompiler.get_std_exe_link_args(self)
  
      def get_include_args(self, path, is_system):
          return CCompiler.get_include_args(self, path, is_system)
  
      def get_module_incdir_args(self):
          return ('-I', )
  
      def get_module_outdir_args(self, path):
          return ['-module' + path]
  
      def module_name_to_filename(self, module_name):
          return module_name.lower() + '.mod'
  
      def get_std_shared_lib_link_args(self):
          return CCompiler.get_std_shared_lib_link_args(self)
  
      def _get_search_dirs(self, *args, **kwargs):
          return CCompiler._get_search_dirs(self, *args, **kwargs)
  
      def get_compiler_dirs(self, *args, **kwargs):
          return CCompiler.get_compiler_dirs(self, *args, **kwargs)
  
      def get_library_dirs(self, *args, **kwargs):
          return CCompiler.get_library_dirs(self, *args, **kwargs)
  
      def get_pic_args(self):
          return CCompiler.get_pic_args(self)
  
      def name_string(self):
          return CCompiler.name_string(self)
  
      def get_linker_search_args(self, dirname):
          return CCompiler.get_linker_search_args(self, dirname)
  
      def get_default_include_dirs(self):
          return CCompiler.get_default_include_dirs(self)
  
      def gen_export_dynamic_link_args(self, env):
          return CCompiler.gen_export_dynamic_link_args(self, env)
  
      def gen_import_library_args(self, implibname):
          return CCompiler.gen_import_library_args(self, implibname)
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          return CCompiler._get_compiler_check_args(self, env, extra_args, dependencies, mode='compile')
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          return CCompiler.compiles(self, code, env, extra_args, dependencies, mode)
  
      def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False):
          return CCompiler._build_wrapper(self, code, env, extra_args, dependencies, mode, want_output)
  
      def links(self, code, env, extra_args=None, dependencies=None):
          return CCompiler.links(self, code, env, extra_args, dependencies)
  
      def run(self, code, env, extra_args=None, dependencies=None):
          return CCompiler.run(self, code, env, extra_args, dependencies)
  
      def _get_patterns(self, *args, **kwargs):
          return CCompiler._get_patterns(self, *args, **kwargs)
  
      def get_library_naming(self, *args, **kwargs):
          return CCompiler.get_library_naming(self, *args, **kwargs)
  
      def find_library_real(self, *args):
          return CCompiler.find_library_real(self, *args)
  
      def find_library_impl(self, *args):
          return CCompiler.find_library_impl(self, *args)
  
      def find_library(self, libname, env, extra_dirs, libtype='default'):
          code = '''program main
              call exit(0)
          end program main'''
          return self.find_library_impl(libname, env, extra_dirs, code, libtype)
  
      def thread_flags(self, env):
          return CCompiler.thread_flags(self, env)
  
      def thread_link_flags(self, env):
          return CCompiler.thread_link_flags(self, env)
  
      def linker_to_compiler_args(self, args):
          return CCompiler.linker_to_compiler_args(self, args)
  
      def has_arguments(self, args, env, code, mode):
          return CCompiler.has_arguments(self, args, env, code, mode)
  
      def has_multi_arguments(self, args, env):
          return CCompiler.has_multi_arguments(self, args, env)
  
  
  class GnuFortranCompiler(GnuCompiler, FortranCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          GnuCompiler.__init__(self, compiler_type, defines)
          default_warn_args = ['-Wall']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_dependency_gen_args(self, outtarget, outfile):
          # Disabled until this is fixed:
          # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162
          # return ['-cpp', '-MD', '-MQ', outtarget]
          return []
  
      def get_module_outdir_args(self, path):
          return ['-J' + path]
  
      def language_stdlib_only_link_flags(self):
          return ['-lgfortran', '-lm']
  
  
  class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuFortranCompiler.__init__(self, exelist, version, compiler_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, compiler_type, defines)
  
  class G95FortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'g95'
          default_warn_args = ['-Wall']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-pedantic']}
  
      def get_module_outdir_args(self, path):
          return ['-fmod=' + path]
  
      def get_no_warn_args(self):
          # FIXME: Confirm that there's no compiler option to disable all warnings
          return []
  
  
  class SunFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'sun'
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return ['-fpp']
  
      def get_always_args(self):
          return []
  
      def get_warn_args(self, level):
          return []
  
      def get_module_incdir_args(self):
          return ('-M', )
  
      def get_module_outdir_args(self, path):
          return ['-moddir=' + path]
  
      def openmp_flags(self):
          return ['-xopenmp']
  
  
  class IntelFortranCompiler(IntelCompiler, FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp')
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          # FIXME: Add support for OS X and Windows in detect_fortran_compiler so
          # we are sent the type of compiler
          IntelCompiler.__init__(self, CompilerType.ICC_STANDARD)
          self.id = 'intel'
          default_warn_args = ['-warn', 'general', '-warn', 'truncated_source']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-warn', 'unused'],
                            '3': ['-warn', 'all']}
  
      def get_preprocess_only_args(self):
          return ['-cpp', '-EP']
  
  
  class PathScaleFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'pathscale'
          default_warn_args = ['-fullwarn']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def openmp_flags(self):
          return ['-mp']
  
  
  class PGIFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'pgi'
          default_warn_args = ['-Minform=inform']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def get_module_incdir_args(self):
          return ('-module', )
  
      def get_no_warn_args(self):
          return ['-silent']
  
      def openmp_flags(self):
          return ['-fopenmp']
  
  
  class Open64FortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'open64'
          default_warn_args = ['-fullwarn']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def openmp_flags(self):
          return ['-mp']
  
  
  class NAGFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'nagfor'
  
      def get_warn_args(self, level):
          return []
  
      def get_module_outdir_args(self, path):
          return ['-mdir', path]
  
      def openmp_flags(self):
          return ['-openmp']
  
files: mesonbuild/compilers/fortran.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 .c import CCompiler
  from .compilers import (
      ICC_STANDARD,
      apple_buildtype_linker_args,
      gnulike_buildtype_args,
      gnulike_buildtype_linker_args,
      gnu_optimization_args,
      clike_debug_args,
      Compiler,
      GnuCompiler,
      ElbrusCompiler,
      IntelCompiler,
  )
  
  from mesonbuild.mesonlib import EnvironmentException, is_osx
  import subprocess, os
  
  class FortranCompiler(Compiler):
      library_dirs_cache = CCompiler.library_dirs_cache
      program_dirs_cache = CCompiler.library_dirs_cache
      find_library_cache = CCompiler.library_dirs_cache
  
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs):
          self.language = 'fortran'
          Compiler.__init__(self, exelist, version, **kwargs)
          cc = CCompiler(exelist, version, is_cross, exe_wrapper, **kwargs)
          self.id = 'unknown'
          self.is_cross = cc.is_cross
          self.exe_wrapper = cc.exe_wrapper
  
      def get_display_language(self):
          return 'Fortran'
  
      def needs_static_linker(self):
          return CCompiler.needs_static_linker(self)
  
      def get_always_args(self):
          return CCompiler.get_always_args(self)
  
      def get_linker_debug_crt_args(self):
          return CCompiler.get_linker_debug_crt_args(self)
  
      def get_no_stdinc_args(self):
          return CCompiler.get_no_stdinc_args(self)
  
      def get_no_stdlib_link_args(self):
          return CCompiler.get_no_stdlib_link_args(self)
  
      def get_warn_args(self, level):
          return CCompiler.get_warn_args(self, level)
  
      def get_no_warn_args(self):
          return CCompiler.get_no_warn_args(self)
  
      def get_soname_args(self, *args):
          return CCompiler.get_soname_args(self, *args)
  
      def sanity_check(self, work_dir, environment):
          source_name = os.path.join(work_dir, 'sanitycheckf.f90')
          binary_name = os.path.join(work_dir, 'sanitycheckf')
          with open(source_name, 'w') as ofile:
              ofile.write('''program prog
       print *, "Fortran compilation is working."
  end program prog
  ''')
          extra_flags = self.get_cross_extra_flags(environment, link=True)
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              if self.exe_wrapper is None:
                  # Can't check if the binaries run so we have to assume they do
                  return
              cmdlist = self.exe_wrapper + [binary_name]
          else:
              cmdlist = [binary_name]
          pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string())
  
      def get_std_warn_args(self, level):
          return FortranCompiler.std_warn_args
  
      def get_buildtype_args(self, buildtype):
          return gnulike_buildtype_args[buildtype]
  
      def get_optimization_args(self, optimization_level):
          return gnu_optimization_args[optimization_level]
  
      def get_debug_args(self, is_debug):
          return clike_debug_args[is_debug]
  
      def get_buildtype_linker_args(self, buildtype):
          if is_osx():
              return apple_buildtype_linker_args[buildtype]
          return gnulike_buildtype_linker_args[buildtype]
  
      def split_shlib_to_parts(self, fname):
          return CCompiler.split_shlib_to_parts(self, fname)
  
      def build_rpath_args(self, *args):
          return CCompiler.build_rpath_args(self, *args)
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return []
  
      def depfile_for_object(self, objfile):
          return CCompiler.depfile_for_object(self, objfile)
  
      def get_depfile_suffix(self):
          return CCompiler.get_depfile_suffix(self)
  
      def get_exelist(self):
          return CCompiler.get_exelist(self)
  
      def get_linker_exelist(self):
          return CCompiler.get_linker_exelist(self)
  
      def get_preprocess_only_args(self):
          return ['-cpp'] + CCompiler.get_preprocess_only_args(self)
  
      def get_compile_only_args(self):
          return CCompiler.get_compile_only_args(self)
  
      def get_no_optimization_args(self):
          return CCompiler.get_no_optimization_args(self)
  
      def get_compiler_check_args(self):
          return CCompiler.get_compiler_check_args(self)
  
      def get_allow_undefined_link_args(self):
          return CCompiler.get_allow_undefined_link_args(self)
  
      def get_output_args(self, target):
          return CCompiler.get_output_args(self, target)
  
      def get_linker_output_args(self, outputname):
          return CCompiler.get_linker_output_args(self, outputname)
  
      def get_coverage_args(self):
          return CCompiler.get_coverage_args(self)
  
      def get_coverage_link_args(self):
          return CCompiler.get_coverage_link_args(self)
  
      def get_werror_args(self):
          return CCompiler.get_werror_args(self)
  
      def get_std_exe_link_args(self):
          return CCompiler.get_std_exe_link_args(self)
  
      def get_include_args(self, path, is_system):
          return CCompiler.get_include_args(self, path, is_system)
  
      def get_module_incdir_args(self):
          return ('-I', )
  
      def get_module_outdir_args(self, path):
          return ['-module' + path]
  
      def module_name_to_filename(self, module_name):
          return module_name.lower() + '.mod'
  
      def get_std_shared_lib_link_args(self):
          return CCompiler.get_std_shared_lib_link_args(self)
  
      def _get_search_dirs(self, *args, **kwargs):
          return CCompiler._get_search_dirs(self, *args, **kwargs)
  
      def get_compiler_dirs(self, *args, **kwargs):
          return CCompiler.get_compiler_dirs(self, *args, **kwargs)
  
      def get_library_dirs(self, *args, **kwargs):
          return CCompiler.get_library_dirs(self, *args, **kwargs)
  
      def get_pic_args(self):
          return CCompiler.get_pic_args(self)
  
      def name_string(self):
          return CCompiler.name_string(self)
  
      def get_linker_search_args(self, dirname):
          return CCompiler.get_linker_search_args(self, dirname)
  
      def get_default_include_dirs(self):
          return CCompiler.get_default_include_dirs(self)
  
      def gen_export_dynamic_link_args(self, env):
          return CCompiler.gen_export_dynamic_link_args(self, env)
  
      def gen_import_library_args(self, implibname):
          return CCompiler.gen_import_library_args(self, implibname)
  
      def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'):
          return CCompiler._get_compiler_check_args(self, env, extra_args, dependencies, mode='compile')
  
      def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
          return CCompiler.compiles(self, code, env, extra_args, dependencies, mode)
  
      def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False):
          return CCompiler._build_wrapper(self, code, env, extra_args, dependencies, mode, want_output)
  
      def links(self, code, env, extra_args=None, dependencies=None):
          return CCompiler.links(self, code, env, extra_args, dependencies)
  
      def run(self, code, env, extra_args=None, dependencies=None):
          return CCompiler.run(self, code, env, extra_args, dependencies)
  
      def _get_patterns(self, *args, **kwargs):
          return CCompiler._get_patterns(self, *args, **kwargs)
  
      def get_library_naming(self, *args, **kwargs):
          return CCompiler.get_library_naming(self, *args, **kwargs)
  
      def find_library_real(self, *args):
          return CCompiler.find_library_real(self, *args)
  
      def find_library_impl(self, *args):
          return CCompiler.find_library_impl(self, *args)
  
      def find_library(self, libname, env, extra_dirs, libtype='default'):
          code = '''program main
              call exit(0)
          end program main'''
          return self.find_library_impl(libname, env, extra_dirs, code, libtype)
  
      def thread_flags(self, env):
          return CCompiler.thread_flags(self, env)
  
      def thread_link_flags(self, env):
          return CCompiler.thread_link_flags(self, env)
  
      def linker_to_compiler_args(self, args):
          return CCompiler.linker_to_compiler_args(self, args)
  
      def has_arguments(self, args, env, code, mode):
          return CCompiler.has_arguments(self, args, env, code, mode)
  
      def has_multi_arguments(self, args, env):
          return CCompiler.has_multi_arguments(self, args, env)
  
  
  class GnuFortranCompiler(GnuCompiler, FortranCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)
          GnuCompiler.__init__(self, gcc_type, defines)
          default_warn_args = ['-Wall']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
      def get_dependency_gen_args(self, outtarget, outfile):
          # Disabled until this is fixed:
          # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162
          # return ['-cpp', '-MD', '-MQ', outtarget]
          return []
  
      def get_module_outdir_args(self, path):
          return ['-J' + path]
  
      def language_stdlib_only_link_flags(self):
          return ['-lgfortran', '-lm']
  
  
  class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs):
          GnuFortranCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs)
          ElbrusCompiler.__init__(self, gcc_type, defines)
  
  class G95FortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'g95'
          default_warn_args = ['-Wall']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-pedantic']}
  
      def get_module_outdir_args(self, path):
          return ['-fmod=' + path]
  
      def get_no_warn_args(self):
          # FIXME: Confirm that there's no compiler option to disable all warnings
          return []
  
  
  class SunFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'sun'
  
      def get_dependency_gen_args(self, outtarget, outfile):
          return ['-fpp']
  
      def get_always_args(self):
          return []
  
      def get_warn_args(self, level):
          return []
  
      def get_module_incdir_args(self):
          return ('-M', )
  
      def get_module_outdir_args(self, path):
          return ['-moddir=' + path]
  
      def openmp_flags(self):
          return ['-xopenmp']
  
  
  class IntelFortranCompiler(IntelCompiler, FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp')
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          # FIXME: Add support for OS X and Windows in detect_fortran_compiler so
          # we are sent the type of compiler
          IntelCompiler.__init__(self, ICC_STANDARD)
          self.id = 'intel'
          default_warn_args = ['-warn', 'general', '-warn', 'truncated_source']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-warn', 'unused'],
                            '3': ['-warn', 'all']}
  
      def get_preprocess_only_args(self):
          return ['-cpp', '-EP']
  
  
  class PathScaleFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'pathscale'
          default_warn_args = ['-fullwarn']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def openmp_flags(self):
          return ['-mp']
  
  
  class PGIFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'pgi'
          default_warn_args = ['-Minform=inform']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def get_module_incdir_args(self):
          return ('-module', )
  
      def get_no_warn_args(self):
          return ['-silent']
  
      def openmp_flags(self):
          return ['-fopenmp']
  
  
  class Open64FortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'open64'
          default_warn_args = ['-fullwarn']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args,
                            '3': default_warn_args}
  
      def openmp_flags(self):
          return ['-mp']
  
  
  class NAGFortranCompiler(FortranCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags):
          FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags)
          self.id = 'nagfor'
  
      def get_warn_args(self, level):
          return []
  
      def get_module_outdir_args(self, path):
          return ['-mdir', path]
  
      def openmp_flags(self):
          return ['-openmp']
  
files: mesonbuild/compilers/fortran.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException
  
  from .c import CCompiler
  from .compilers import ClangCompiler, GnuCompiler
  
  class ObjCCompiler(CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap):
          self.language = 'objc'
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
  
      def get_display_language(self):
          return 'Objective-C'
  
      def sanity_check(self, work_dir, environment):
          # TODO try to use sanity_check_impl instead of duplicated code
          source_name = os.path.join(work_dir, 'sanitycheckobjc.m')
          binary_name = os.path.join(work_dir, 'sanitycheckobjc')
          extra_flags = self.get_cross_extra_flags(environment, link=False)
          if self.is_cross:
              extra_flags += self.get_compile_only_args()
          with open(source_name, 'w') as ofile:
              ofile.write('#import<stdio.h>\n'
                          'int main(int argc, char **argv) { return 0; }\n')
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('ObjC compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              # Can't check if the binaries run so we have to assume they do
              return
          pe = subprocess.Popen(binary_name)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by ObjC compiler %s are not runnable.' % self.name_string())
  
  
  class GnuObjCCompiler(GnuCompiler, ObjCCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None):
          ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          GnuCompiler.__init__(self, compiler_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
  
  class ClangObjCCompiler(ClangCompiler, ObjCCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None):
          ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          ClangCompiler.__init__(self, compiler_type)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
  
files: mesonbuild/compilers/objc.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException
  
  from .c import CCompiler
  from .compilers import ClangCompiler, GnuCompiler
  
  class ObjCCompiler(CCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap):
          self.language = 'objc'
          CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
  
      def get_display_language(self):
          return 'Objective-C'
  
      def sanity_check(self, work_dir, environment):
          # TODO try to use sanity_check_impl instead of duplicated code
          source_name = os.path.join(work_dir, 'sanitycheckobjc.m')
          binary_name = os.path.join(work_dir, 'sanitycheckobjc')
          extra_flags = self.get_cross_extra_flags(environment, link=False)
          if self.is_cross:
              extra_flags += self.get_compile_only_args()
          with open(source_name, 'w') as ofile:
              ofile.write('#import<stdio.h>\n'
                          'int main(int argc, char **argv) { return 0; }\n')
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('ObjC compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              # Can't check if the binaries run so we have to assume they do
              return
          pe = subprocess.Popen(binary_name)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by ObjC compiler %s are not runnable.' % self.name_string())
  
  
  class GnuObjCCompiler(GnuCompiler, ObjCCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None):
          ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          GnuCompiler.__init__(self, gcc_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
  
  class ClangObjCCompiler(ClangCompiler, GnuObjCCompiler):
      def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None):
          GnuObjCCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper)
          ClangCompiler.__init__(self, cltype)
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
  
files: mesonbuild/compilers/objc.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException
  
  from .cpp import CPPCompiler
  from .compilers import ClangCompiler, GnuCompiler
  
  class ObjCPPCompiler(CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap):
          self.language = 'objcpp'
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
  
      def get_display_language(self):
          return 'Objective-C++'
  
      def sanity_check(self, work_dir, environment):
          # TODO try to use sanity_check_impl instead of duplicated code
          source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm')
          binary_name = os.path.join(work_dir, 'sanitycheckobjcpp')
          extra_flags = self.get_cross_extra_flags(environment, link=False)
          if self.is_cross:
              extra_flags += self.get_compile_only_args()
          with open(source_name, 'w') as ofile:
              ofile.write('#import<stdio.h>\n'
                          'class MyClass;'
                          'int main(int argc, char **argv) { return 0; }\n')
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('ObjC++ compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              # Can't check if the binaries run so we have to assume they do
              return
          pe = subprocess.Popen(binary_name)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by ObjC++ compiler %s are not runnable.' % self.name_string())
  
  
  class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None):
          ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          GnuCompiler.__init__(self, compiler_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
  
  class ClangObjCPPCompiler(ClangCompiler, ObjCPPCompiler):
      def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None):
          ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          ClangCompiler.__init__(self, compiler_type)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
  
files: mesonbuild/compilers/objcpp.py
files: # Copyright 2012-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import os.path, subprocess
  
  from ..mesonlib import EnvironmentException
  
  from .cpp import CPPCompiler
  from .compilers import ClangCompiler, GnuCompiler
  
  class ObjCPPCompiler(CPPCompiler):
      def __init__(self, exelist, version, is_cross, exe_wrap):
          self.language = 'objcpp'
          CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
  
      def get_display_language(self):
          return 'Objective-C++'
  
      def sanity_check(self, work_dir, environment):
          # TODO try to use sanity_check_impl instead of duplicated code
          source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm')
          binary_name = os.path.join(work_dir, 'sanitycheckobjcpp')
          extra_flags = self.get_cross_extra_flags(environment, link=False)
          if self.is_cross:
              extra_flags += self.get_compile_only_args()
          with open(source_name, 'w') as ofile:
              ofile.write('#import<stdio.h>\n'
                          'class MyClass;'
                          'int main(int argc, char **argv) { return 0; }\n')
          pc = subprocess.Popen(self.exelist + extra_flags + [source_name, '-o', binary_name])
          pc.wait()
          if pc.returncode != 0:
              raise EnvironmentException('ObjC++ compiler %s can not compile programs.' % self.name_string())
          if self.is_cross:
              # Can't check if the binaries run so we have to assume they do
              return
          pe = subprocess.Popen(binary_name)
          pe.wait()
          if pe.returncode != 0:
              raise EnvironmentException('Executables created by ObjC++ compiler %s are not runnable.' % self.name_string())
  
  
  class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler):
      def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None):
          ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
          GnuCompiler.__init__(self, gcc_type, defines)
          default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
          self.warn_args = {'1': default_warn_args,
                            '2': default_warn_args + ['-Wextra'],
                            '3': default_warn_args + ['-Wextra', '-Wpedantic']}
  
  
  class ClangObjCPPCompiler(ClangCompiler, GnuObjCPPCompiler):
      def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None):
          GnuObjCPPCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper)
          ClangCompiler.__init__(self, cltype)
          self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
  
files: mesonbuild/compilers/objcpp.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 configparser, os, platform, re, shlex, shutil, subprocess
  
  from . import coredata
  from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker
  from . import mesonlib
  from .mesonlib import EnvironmentException, Popen_safe
  from . import mlog
  
  from . import compilers
  from .compilers import (
      CompilerType,
      is_assembly,
      is_header,
      is_library,
      is_llvm_ir,
      is_object,
      is_source,
  )
  from .compilers import (
      ArmCCompiler,
      ArmCPPCompiler,
      ArmclangCCompiler,
      ArmclangCPPCompiler,
      ClangCCompiler,
      ClangCPPCompiler,
      ClangObjCCompiler,
      ClangObjCPPCompiler,
      G95FortranCompiler,
      GnuCCompiler,
      GnuCPPCompiler,
      GnuFortranCompiler,
      GnuObjCCompiler,
      GnuObjCPPCompiler,
      ElbrusCCompiler,
      ElbrusCPPCompiler,
      ElbrusFortranCompiler,
      IntelCCompiler,
      IntelCPPCompiler,
      IntelFortranCompiler,
      JavaCompiler,
      MonoCompiler,
      VisualStudioCsCompiler,
      NAGFortranCompiler,
      Open64FortranCompiler,
      PathScaleFortranCompiler,
      PGIFortranCompiler,
      RustCompiler,
      SunFortranCompiler,
      ValaCompiler,
      VisualStudioCCompiler,
      VisualStudioCPPCompiler,
  )
  
  build_filename = 'meson.build'
  
  known_cpu_families = (
      'aarch64',
      'arm',
      'e2k',
      'ia64',
      'mips',
      'mips64',
      'parisc',
      'ppc',
      'ppc64',
      'riscv32',
      'riscv64',
      's390x',
      'sparc',
      'sparc64',
      'x86',
      'x86_64'
  )
  
  def detect_gcovr(version='3.1', log=False):
      gcovr_exe = 'gcovr'
      try:
          p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
      except (FileNotFoundError, PermissionError):
          # Doesn't exist in PATH or isn't executable
          return None, None
      found = search_version(found)
      if p.returncode == 0:
          if log:
              mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe))))
          return gcovr_exe, mesonlib.version_compare(found, '>=' + version)
      return None, None
  
  def find_coverage_tools():
      gcovr_exe, gcovr_new_rootdir = detect_gcovr()
  
      lcov_exe = 'lcov'
      genhtml_exe = 'genhtml'
  
      if not mesonlib.exe_exists([lcov_exe, '--version']):
          lcov_exe = None
      if not mesonlib.exe_exists([genhtml_exe, '--version']):
          genhtml_exe = None
  
      return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe
  
  def detect_ninja(version='1.5', log=False):
      for n in ['ninja', 'ninja-build']:
          try:
              p, found = Popen_safe([n, '--version'])[0:2]
          except (FileNotFoundError, PermissionError):
              # Doesn't exist in PATH or isn't executable
              continue
          found = found.strip()
          # Perhaps we should add a way for the caller to know the failure mode
          # (not found or too old)
          if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
              if log:
                  mlog.log('Found ninja-{} at {}'.format(found, shlex.quote(shutil.which(n))))
              return n
  
  def detect_native_windows_arch():
      """
      The architecture of Windows itself: x86 or amd64
      """
      # These env variables are always available. See:
      # https://msdn.microsoft.com/en-us/library/aa384274(VS.85).aspx
      # https://blogs.msdn.microsoft.com/david.wang/2006/03/27/howto-detect-process-bitness/
      arch = os.environ.get('PROCESSOR_ARCHITEW6432', '').lower()
      if not arch:
          try:
              # If this doesn't exist, something is messing with the environment
              arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()
          except KeyError:
              raise EnvironmentException('Unable to detect native OS architecture')
      return arch
  
  def detect_windows_arch(compilers):
      """
      Detecting the 'native' architecture of Windows is not a trivial task. We
      cannot trust that the architecture that Python is built for is the 'native'
      one because you can run 32-bit apps on 64-bit Windows using WOW64 and
      people sometimes install 32-bit Python on 64-bit Windows.
  
      We also can't rely on the architecture of the OS itself, since it's
      perfectly normal to compile and run 32-bit applications on Windows as if
      they were native applications. It's a terrible experience to require the
      user to supply a cross-info file to compile 32-bit applications on 64-bit
      Windows. Thankfully, the only way to compile things with Visual Studio on
      Windows is by entering the 'msvc toolchain' environment, which can be
      easily detected.
  
      In the end, the sanest method is as follows:
      1. Check if we're in an MSVC toolchain environment, and if so, return the
         MSVC toolchain architecture as our 'native' architecture.
      2. If not, check environment variables that are set by Windows and WOW64 to
         find out the architecture that Windows is built for, and use that as our
         'native' architecture.
      """
      os_arch = detect_native_windows_arch()
      if os_arch != 'amd64':
          return os_arch
      # If we're on 64-bit Windows, 32-bit apps can be compiled without
      # cross-compilation. So if we're doing that, just set the native arch as
      # 32-bit and pretend like we're running under WOW64. Else, return the
      # actual Windows architecture that we deduced above.
      for compiler in compilers.values():
          # Check if we're using and inside an MSVC toolchain environment
          if compiler.id == 'msvc' and 'VCINSTALLDIR' in os.environ:
              if float(compiler.get_toolset_version()) < 10.0:
                  # On MSVC 2008 and earlier, check 'BUILD_PLAT', where
                  # 'Win32' means 'x86'
                  platform = os.environ.get('BUILD_PLAT', 'x86')
                  if platform == 'Win32':
                      return 'x86'
              else:
                  # On MSVC 2010 and later 'Platform' is only set when the
                  # target arch is not 'x86'.  It's 'x64' when targeting
                  # x86_64 and 'arm' when targeting ARM.
                  platform = os.environ.get('Platform', 'x86').lower()
              if platform == 'x86':
                  return platform
          if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'):
              return 'x86'
      return os_arch
  
  def detect_cpu_family(compilers):
      """
      Python is inconsistent in its platform module.
      It returns different values for the same cpu.
      For x86 it might return 'x86', 'i686' or somesuch.
      Do some canonicalization.
      """
      if mesonlib.is_windows():
          trial = detect_windows_arch(compilers)
      else:
          trial = platform.machine().lower()
      if trial.startswith('i') and trial.endswith('86'):
          return 'x86'
      if trial.startswith('arm'):
          return 'arm'
      if trial.startswith('ppc64'):
          return 'ppc64'
      if trial in ('amd64', 'x64'):
          trial = 'x86_64'
      if trial == 'x86_64':
          # On Linux (and maybe others) there can be any mixture of 32/64 bit
          # code in the kernel, Python, system etc. The only reliable way
          # to know is to check the compiler defines.
          for c in compilers.values():
              try:
                  if c.has_builtin_define('__i386__'):
                      return 'x86'
              except mesonlib.MesonException:
                  # Ignore compilers that do not support has_builtin_define.
                  pass
          return 'x86_64'
      # Add fixes here as bugs are reported.
  
      if trial not in known_cpu_families:
          mlog.warning('Unknown CPU family {!r}, please report this at '
                       'https://github.com/mesonbuild/meson/issues/new with the'
                       'output of `uname -a` and `cat /proc/cpuinfo`'.format(trial))
  
      return trial
  
  def detect_cpu(compilers):
      if mesonlib.is_windows():
          trial = detect_windows_arch(compilers)
      else:
          trial = platform.machine().lower()
      if trial in ('amd64', 'x64'):
          trial = 'x86_64'
      if trial == 'x86_64':
          # Same check as above for cpu_family
          for c in compilers.values():
              try:
                  if c.has_builtin_define('__i386__'):
                      return 'i686' # All 64 bit cpus have at least this level of x86 support.
              except mesonlib.MesonException:
                  pass
          return 'x86_64'
      if trial == 'e2k':
          # Make more precise CPU detection for Elbrus platform.
          trial = platform.processor().lower()
      # Add fixes here as bugs are reported.
      return trial
  
  def detect_system():
      system = platform.system().lower()
      if system.startswith('cygwin'):
          return 'cygwin'
      return system
  
  def detect_msys2_arch():
      if 'MSYSTEM_CARCH' in os.environ:
          return os.environ['MSYSTEM_CARCH']
      return None
  
  def search_version(text):
      # Usually of the type 4.1.4 but compiler output may contain
      # stuff like this:
      # (Sourcery CodeBench Lite 2014.05-29) 4.8.3 20140320 (prerelease)
      # Limiting major version number to two digits seems to work
      # thus far. When we get to GCC 100, this will break, but
      # if we are still relevant when that happens, it can be
      # considered an achievement in itself.
      #
      # This regex is reaching magic levels. If it ever needs
      # to be updated, do not complexify but convert to something
      # saner instead.
      version_regex = '(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)'
      match = re.search(version_regex, text)
      if match:
          return match.group(0)
      return 'unknown version'
  
  class Environment:
      private_dir = 'meson-private'
      log_dir = 'meson-logs'
  
      def __init__(self, source_dir, build_dir, options):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.scratch_dir = os.path.join(build_dir, Environment.private_dir)
          self.log_dir = os.path.join(build_dir, Environment.log_dir)
          os.makedirs(self.scratch_dir, exist_ok=True)
          os.makedirs(self.log_dir, exist_ok=True)
          try:
              self.coredata = coredata.load(self.get_build_dir())
              self.first_invocation = False
          except FileNotFoundError:
              # WARNING: Don't use any values from coredata in __init__. It gets
              # re-initialized with project options by the interpreter during
              # build file parsing.
              self.coredata = coredata.CoreData(options)
              # Used by the regenchecker script, which runs meson
              self.coredata.meson_command = mesonlib.meson_command
              self.first_invocation = True
          self.cross_info = None
          self.exe_wrapper = None
          if self.coredata.cross_file:
              self.cross_info = CrossBuildInfo(self.coredata.cross_file)
              if 'exe_wrapper' in self.cross_info.config['binaries']:
                  from .dependencies import ExternalProgram
                  self.exe_wrapper = ExternalProgram.from_cross_info(self.cross_info, 'exe_wrapper')
          self.cmd_line_options = options.cmd_line_options.copy()
  
          # List of potential compilers.
          if mesonlib.is_windows():
              self.default_c = ['cl', 'cc', 'gcc', 'clang']
              self.default_cpp = ['cl', 'c++', 'g++', 'clang++']
          else:
              self.default_c = ['cc', 'gcc', 'clang']
              self.default_cpp = ['c++', 'g++', 'clang++']
          if mesonlib.is_windows():
              self.default_cs = ['csc', 'mcs']
          else:
              self.default_cs = ['mcs', 'csc']
          self.default_objc = ['cc']
          self.default_objcpp = ['c++']
          self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort']
          self.default_rust = ['rustc']
          self.default_static_linker = ['ar']
          self.vs_static_linker = ['lib']
          self.gcc_static_linker = ['gcc-ar']
          self.clang_static_linker = ['llvm-ar']
  
          # Various prefixes and suffixes for import libraries, shared libraries,
          # static libraries, and executables.
          # Versioning is added to these names in the backends as-needed.
          cross = self.is_cross_build()
          if mesonlib.for_windows(cross, self):
              self.exe_suffix = 'exe'
              self.object_suffix = 'obj'
              self.win_libdir_layout = True
          elif mesonlib.for_cygwin(cross, self):
              self.exe_suffix = 'exe'
              self.object_suffix = 'o'
              self.win_libdir_layout = True
          else:
              self.exe_suffix = ''
              self.object_suffix = 'o'
              self.win_libdir_layout = False
          if 'STRIP' in os.environ:
              self.native_strip_bin = shlex.split(os.environ['STRIP'])
          else:
              self.native_strip_bin = ['strip']
  
      def is_cross_build(self):
          return self.cross_info is not None
  
      def dump_coredata(self):
          return coredata.save(self.coredata, self.get_build_dir())
  
      def get_script_dir(self):
          import mesonbuild.scripts
          return os.path.dirname(mesonbuild.scripts.__file__)
  
      def get_log_dir(self):
          return self.log_dir
  
      def get_coredata(self):
          return self.coredata
  
      def get_build_command(self, unbuffered=False):
          cmd = mesonlib.meson_command[:]
          if unbuffered and 'python' in cmd[0]:
              cmd.insert(1, '-u')
          return cmd
  
      def is_header(self, fname):
          return is_header(fname)
  
      def is_source(self, fname):
          return is_source(fname)
  
      def is_assembly(self, fname):
          return is_assembly(fname)
  
      def is_llvm_ir(self, fname):
          return is_llvm_ir(fname)
  
      def is_object(self, fname):
          return is_object(fname)
  
      def is_library(self, fname):
          return is_library(fname)
  
      @staticmethod
      def get_gnu_compiler_defines(compiler):
          """
          Detect GNU compiler platform type (Apple, MinGW, Unix)
          """
          # Arguments to output compiler pre-processor defines to stdout
          # gcc, g++, and gfortran all support these arguments
          args = compiler + ['-E', '-dM', '-']
          p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE)
          if p.returncode != 0:
              raise EnvironmentException('Unable to detect GNU compiler type:\n' + output + error)
          # Parse several lines of the type:
          # `#define ___SOME_DEF some_value`
          # and extract `___SOME_DEF`
          defines = {}
          for line in output.split('\n'):
              if not line:
                  continue
              d, *rest = line.split(' ', 2)
              if d != '#define':
                  continue
              if len(rest) == 1:
                  defines[rest] = True
              if len(rest) == 2:
                  defines[rest[0]] = rest[1]
          return defines
  
      @staticmethod
      def get_gnu_version_from_defines(defines):
          dot = '.'
          major = defines.get('__GNUC__', '0')
          minor = defines.get('__GNUC_MINOR__', '0')
          patch = defines.get('__GNUC_PATCHLEVEL__', '0')
          return dot.join((major, minor, patch))
  
      @staticmethod
      def get_lcc_version_from_defines(defines):
          dot = '.'
          generation_and_major = defines.get('__LCC__', '100')
          generation = generation_and_major[:1]
          major = generation_and_major[1:]
          minor = defines.get('__LCC_MINOR__', '0')
          return dot.join((generation, major, minor))
  
      @staticmethod
      def get_gnu_compiler_type(defines):
          # Detect GCC type (Apple, MinGW, Cygwin, Unix)
          if '__APPLE__' in defines:
              return CompilerType.GCC_OSX
          elif '__MINGW32__' in defines or '__MINGW64__' in defines:
              return CompilerType.GCC_MINGW
          elif '__CYGWIN__' in defines:
              return CompilerType.GCC_CYGWIN
          return CompilerType.GCC_STANDARD
  
      def warn_about_lang_pointing_to_cross(self, compiler_exe, evar):
          evar_str = os.environ.get(evar, 'WHO_WOULD_CALL_THEIR_COMPILER_WITH_THIS_NAME')
          if evar_str == compiler_exe:
              mlog.warning('''Env var %s seems to point to the cross compiler.
  This is probably wrong, it should always point to the native compiler.''' % evar)
  
      def _get_compilers(self, lang, evar, want_cross):
          '''
          The list of compilers is detected in the exact same way for
          C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here.
          '''
          if self.is_cross_build() and want_cross:
              if lang not in self.cross_info.config['binaries']:
                  raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang))
              compilers = mesonlib.stringlistify(self.cross_info.config['binaries'][lang])
              # Ensure ccache exists and remove it if it doesn't
              if compilers[0] == 'ccache':
                  compilers = compilers[1:]
                  ccache = self.detect_ccache()
              else:
                  ccache = []
              self.warn_about_lang_pointing_to_cross(compilers[0], evar)
              # Return value has to be a list of compiler 'choices'
              compilers = [compilers]
              is_cross = True
              exe_wrap = self.get_exe_wrapper()
          elif evar in os.environ:
              compilers = shlex.split(os.environ[evar])
              # Ensure ccache exists and remove it if it doesn't
              if compilers[0] == 'ccache':
                  compilers = compilers[1:]
                  ccache = self.detect_ccache()
              else:
                  ccache = []
              # Return value has to be a list of compiler 'choices'
              compilers = [compilers]
              is_cross = False
              exe_wrap = None
          else:
              compilers = getattr(self, 'default_' + lang)
              ccache = self.detect_ccache()
              is_cross = False
              exe_wrap = None
          return compilers, ccache, is_cross, exe_wrap
  
      def _handle_exceptions(self, exceptions, binaries, bintype='compiler'):
          errmsg = 'Unknown {}(s): {}'.format(bintype, binaries)
          if exceptions:
              errmsg += '\nThe follow exceptions were encountered:'
              for (c, e) in exceptions.items():
                  errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
          raise EnvironmentException(errmsg)
  
      def _detect_c_or_cpp_compiler(self, lang, evar, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers(lang, evar, want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              if 'cl' in compiler or 'cl.exe' in compiler:
                  # Watcom C provides it's own cl.exe clone that mimics an older
                  # version of Microsoft's compiler. Since Watcom's cl.exe is
                  # just a wrapper, we skip using it if we detect its presence
                  # so as not to confuse Meson when configuring for MSVC.
                  #
                  # Additionally the help text of Watcom's cl.exe is paged, and
                  # the binary will not exit without human intervention. In
                  # practice, Meson will block waiting for Watcom's cl.exe to
                  # exit, which requires user input and thus will never exit.
                  if 'WATCOM' in os.environ:
                      def sanitize(p):
                          return os.path.normcase(os.path.abspath(p))
  
                      watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')),
                                    sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe'))]
                      found_cl = sanitize(shutil.which('cl'))
                      if found_cl in watcom_cls:
                          continue
                  arg = '/?'
              elif 'armcc' in compiler[0]:
                  arg = '--vsn'
              else:
                  arg = '--version'
              try:
                  p, out, err = Popen_safe(compiler + [arg])
              except OSError as e:
                  popen_exceptions[' '.join(compiler + [arg])] = e
                  continue
              version = search_version(out)
              full_version = out.split('\n', 1)[0]
  
              guess_gcc_or_lcc = False
              if 'Free Software Foundation' in out:
                  guess_gcc_or_lcc = 'gcc'
              if 'e2k' in out and 'lcc' in out:
                  guess_gcc_or_lcc = 'lcc'
  
              if guess_gcc_or_lcc:
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  compiler_type = self.get_gnu_compiler_type(defines)
                  if guess_gcc_or_lcc == 'lcc':
                      version = self.get_lcc_version_from_defines(defines)
                      cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler
                  else:
                      version = self.get_gnu_version_from_defines(defines)
                      cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
                  return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap, defines, full_version=full_version)
  
              if 'armclang' in out:
                  # The compiler version is not present in the first line of output,
                  # instead it is present in second line, startswith 'Component:'.
                  # So, searching for the 'Component' in out although we know it is
                  # present in second line, as we are not sure about the
                  # output format in future versions
                  arm_ver_str = re.search('.*Component.*', out)
                  if arm_ver_str is None:
                      popen_exceptions[' '.join(compiler)] = 'version string not found'
                      continue
                  arm_ver_str = arm_ver_str.group(0)
                  # Override previous values
                  version = search_version(arm_ver_str)
                  full_version = arm_ver_str
                  cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler
                  return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
              if 'clang' in out:
                  if 'Apple' in out or mesonlib.for_darwin(want_cross, self):
                      compiler_type = CompilerType.CLANG_OSX
                  elif 'windows' in out or mesonlib.for_windows(want_cross, self):
                      compiler_type = CompilerType.CLANG_MINGW
                  else:
                      compiler_type = CompilerType.CLANG_STANDARD
                  cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler
                  return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap, full_version=full_version)
              if 'Microsoft' in out or 'Microsoft' in err:
                  # Latest versions of Visual Studio print version
                  # number to stderr but earlier ones print version
                  # on stdout.  Why? Lord only knows.
                  # Check both outputs to figure out version.
                  version = search_version(err)
                  if version == 'unknown version':
                      version = search_version(out)
                  if version == 'unknown version':
                      m = 'Failed to detect MSVC compiler arch: stderr was\n{!r}'
                      raise EnvironmentException(m.format(err))
                  is_64 = err.split('\n')[0].endswith(' x64')
                  cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
                  return cls(compiler, version, is_cross, exe_wrap, is_64)
              if '(ICC)' in out:
                  # TODO: add microsoft add check OSX
                  compiler_type = CompilerType.ICC_STANDARD
                  cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
                  return cls(ccache + compiler, version, compiler_type, is_cross, exe_wrap, full_version=full_version)
              if 'ARM' in out:
                  cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler
                  return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_c_compiler(self, want_cross):
          return self._detect_c_or_cpp_compiler('c', 'CC', want_cross)
  
      def detect_cpp_compiler(self, want_cross):
          return self._detect_c_or_cpp_compiler('cpp', 'CXX', want_cross)
  
      def detect_fortran_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('fortran', 'FC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              for arg in ['--version', '-V']:
                  try:
                      p, out, err = Popen_safe(compiler + [arg])
                  except OSError as e:
                      popen_exceptions[' '.join(compiler + [arg])] = e
                      continue
  
                  version = search_version(out)
                  full_version = out.split('\n', 1)[0]
  
                  guess_gcc_or_lcc = False
                  if 'GNU Fortran' in out:
                      guess_gcc_or_lcc = 'gcc'
                  if 'e2k' in out and 'lcc' in out:
                      guess_gcc_or_lcc = 'lcc'
  
                  if guess_gcc_or_lcc:
                      defines = self.get_gnu_compiler_defines(compiler)
                      if not defines:
                          popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                          continue
                      compiler_type = self.get_gnu_compiler_type(defines)
                      if guess_gcc_or_lcc == 'lcc':
                          version = self.get_lcc_version_from_defines(defines)
                          cls = ElbrusFortranCompiler
                      else:
                          version = self.get_gnu_version_from_defines(defines)
                          cls = GnuFortranCompiler
                      return cls(compiler, version, compiler_type, is_cross, exe_wrap, defines, full_version=full_version)
  
                  if 'G95' in out:
                      return G95FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'Sun Fortran' in err:
                      version = search_version(err)
                      return SunFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'ifort (IFORT)' in out:
                      return IntelFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'PathScale EKOPath(tm)' in err:
                      return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'PGI Compilers' in out:
                      return PGIFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'Open64 Compiler Suite' in err:
                      return Open64FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'NAG Fortran' in err:
                      return NAGFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def get_scratch_dir(self):
          return self.scratch_dir
  
      def detect_objc_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('objc', 'OBJC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out, err = Popen_safe(compiler + arg)
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
              version = search_version(out)
              if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  compiler_type = self.get_gnu_compiler_type(defines)
                  version = self.get_gnu_version_from_defines(defines)
                  return GnuObjCCompiler(ccache + compiler, version, compiler_type, is_cross, exe_wrap, defines)
              if out.startswith('Apple LLVM'):
                  return ClangObjCCompiler(ccache + compiler, version, CompilerType.CLANG_OSX, is_cross, exe_wrap)
              if out.startswith('clang'):
                  return ClangObjCCompiler(ccache + compiler, version, CompilerType.CLANG_STANDARD, is_cross, exe_wrap)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_objcpp_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('objcpp', 'OBJCXX', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out, err = Popen_safe(compiler + arg)
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
              version = search_version(out)
              if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  compiler_type = self.get_gnu_compiler_type(defines)
                  version = self.get_gnu_version_from_defines(defines)
                  return GnuObjCPPCompiler(ccache + compiler, version, compiler_type, is_cross, exe_wrap, defines)
              if out.startswith('Apple LLVM'):
                  return ClangObjCPPCompiler(ccache + compiler, version, CompilerType.CLANG_OSX, is_cross, exe_wrap)
              if out.startswith('clang'):
                  return ClangObjCPPCompiler(ccache + compiler, version, CompilerType.CLANG_STANDARD, is_cross, exe_wrap)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_java_compiler(self):
          exelist = ['javac']
          try:
              p, out, err = Popen_safe(exelist + ['-version'])
          except OSError:
              raise EnvironmentException('Could not execute Java compiler "%s"' % ' '.join(exelist))
          version = search_version(err)
          if 'javac' in out or 'javac' in err:
              return JavaCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_cs_compiler(self):
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('cs', 'CSC', False)
          popen_exceptions = {}
          for comp in compilers:
              if not isinstance(comp, list):
                  comp = [comp]
              try:
                  p, out, err = Popen_safe(comp + ['--version'])
              except OSError as e:
                  popen_exceptions[' '.join(comp + ['--version'])] = e
                  continue
  
              version = search_version(out)
              if 'Mono' in out:
                  return MonoCompiler(comp, version)
              elif "Visual C#" in out:
                  return VisualStudioCsCompiler(comp, version)
  
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_vala_compiler(self):
          if 'VALAC' in os.environ:
              exelist = shlex.split(os.environ['VALAC'])
          else:
              exelist = ['valac']
          try:
              p, out = Popen_safe(exelist + ['--version'])[0:2]
          except OSError:
              raise EnvironmentException('Could not execute Vala compiler "%s"' % ' '.join(exelist))
          version = search_version(out)
          if 'Vala' in out:
              return ValaCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_rust_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('rust', 'RUSTC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out = Popen_safe(compiler + arg)[0:2]
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
  
              version = search_version(out)
  
              if 'rustc' in out:
                  return RustCompiler(compiler, version, is_cross, exe_wrap)
  
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_d_compiler(self, want_cross):
          is_cross = False
          # Search for a D compiler.
          # We prefer LDC over GDC unless overridden with the DC
          # environment variable because LDC has a much more
          # up to date language version at time (2016).
          if 'DC' in os.environ:
              exelist = shlex.split(os.environ['DC'])
              if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')):
                      raise EnvironmentException('Meson doesn\'t support %s as it\'s only a DMD frontend for another compiler. Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.' % exelist[-1])
          elif self.is_cross_build() and want_cross:
              exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d'])
              is_cross = True
          elif shutil.which("ldc2"):
              exelist = ['ldc2']
          elif shutil.which("ldc"):
              exelist = ['ldc']
          elif shutil.which("gdc"):
              exelist = ['gdc']
          elif shutil.which("dmd"):
              exelist = ['dmd']
          else:
              raise EnvironmentException('Could not find any supported D compiler.')
  
          try:
              p, out = Popen_safe(exelist + ['--version'])[0:2]
          except OSError:
              raise EnvironmentException('Could not execute D compiler "%s"' % ' '.join(exelist))
          version = search_version(out)
          full_version = out.split('\n', 1)[0]
  
          # Detect which MSVC build environment is currently active.
          is_64 = False
          c_compiler = {}
          if mesonlib.is_windows() and 'VCINSTALLDIR' in os.environ:
              # MSVC compiler is required for correct platform detection.
              c_compiler = {'c': self.detect_c_compiler(want_cross)}
  
          if detect_cpu_family(c_compiler) == 'x86_64':
              is_64 = True
  
          if 'LLVM D compiler' in out:
              return compilers.LLVMDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          elif 'gdc' in out:
              return compilers.GnuDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          elif 'The D Language Foundation' in out or 'Digital Mars' in out:
              return compilers.DmdDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_swift_compiler(self):
          exelist = ['swiftc']
          try:
              p, _, err = Popen_safe(exelist + ['-v'])
          except OSError:
              raise EnvironmentException('Could not execute Swift compiler "%s"' % ' '.join(exelist))
          version = search_version(err)
          if 'Swift' in err:
              return compilers.SwiftCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_static_linker(self, compiler):
          if compiler.is_cross:
              linker = self.cross_info.config['binaries']['ar']
              if isinstance(linker, str):
                  linker = [linker]
              linkers = [linker]
          else:
              evar = 'AR'
              if evar in os.environ:
                  linkers = [shlex.split(os.environ[evar])]
              elif isinstance(compiler, compilers.VisualStudioCCompiler):
                  linkers = [self.vs_static_linker]
              elif isinstance(compiler, compilers.GnuCompiler):
                  # Use gcc-ar if available; needed for LTO
                  linkers = [self.gcc_static_linker, self.default_static_linker]
              elif isinstance(compiler, compilers.ClangCompiler):
                  # Use llvm-ar if available; needed for LTO
                  linkers = [self.clang_static_linker, self.default_static_linker]
              elif isinstance(compiler, compilers.DCompiler):
                  # Prefer static linkers over linkers used by D compilers
                  if mesonlib.is_windows():
                      linkers = [self.vs_static_linker, compiler.get_linker_exelist()]
                  else:
                      linkers = [self.default_static_linker, compiler.get_linker_exelist()]
              else:
                  linkers = [self.default_static_linker]
          popen_exceptions = {}
          for linker in linkers:
              if 'lib' in linker or 'lib.exe' in linker:
                  arg = '/?'
              else:
                  arg = '--version'
              try:
                  p, out, err = Popen_safe(linker + [arg])
              except OSError as e:
                  popen_exceptions[' '.join(linker + [arg])] = e
                  continue
              if '/OUT:' in out or '/OUT:' in err:
                  return VisualStudioLinker(linker)
              if p.returncode == 0 and ('armar' in linker or 'armar.exe' in linker):
                  return ArmarLinker(linker)
              if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if 'LDC - the LLVM D compiler' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if 'GDC' in out and ' based on D ' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if p.returncode == 0:
                  return ArLinker(linker)
              if p.returncode == 1 and err.startswith('usage'): # OSX
                  return ArLinker(linker)
          self._handle_exceptions(popen_exceptions, linkers, 'linker')
          raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linkers))
  
      def detect_ccache(self):
          try:
              has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          except OSError:
              has_ccache = 1
          if has_ccache == 0:
              cmdlist = ['ccache']
          else:
              cmdlist = []
          return cmdlist
  
      def get_source_dir(self):
          return self.source_dir
  
      def get_build_dir(self):
          return self.build_dir
  
      def get_exe_suffix(self):
          return self.exe_suffix
  
      def get_import_lib_dir(self):
          "Install dir for the import library (library used for linking)"
          return self.get_libdir()
  
      def get_shared_module_dir(self):
          "Install dir for shared modules that are loaded at runtime"
          return self.get_libdir()
  
      def get_shared_lib_dir(self):
          "Install dir for the shared library"
          if self.win_libdir_layout:
              return self.get_bindir()
          return self.get_libdir()
  
      def get_static_lib_dir(self):
          "Install dir for the static library"
          return self.get_libdir()
  
      def get_object_suffix(self):
          return self.object_suffix
  
      def get_prefix(self):
          return self.coredata.get_builtin_option('prefix')
  
      def get_libdir(self):
          return self.coredata.get_builtin_option('libdir')
  
      def get_libexecdir(self):
          return self.coredata.get_builtin_option('libexecdir')
  
      def get_bindir(self):
          return self.coredata.get_builtin_option('bindir')
  
      def get_includedir(self):
          return self.coredata.get_builtin_option('includedir')
  
      def get_mandir(self):
          return self.coredata.get_builtin_option('mandir')
  
      def get_datadir(self):
          return self.coredata.get_builtin_option('datadir')
  
      def get_compiler_system_dirs(self):
          for comp in self.coredata.compilers.values():
              if isinstance(comp, compilers.ClangCompiler):
                  index = 1
                  break
              elif isinstance(comp, compilers.GnuCompiler):
                  index = 2
                  break
          else:
              # This option is only supported by gcc and clang. If we don't get a
              # GCC or Clang compiler return and empty list.
              return []
  
          p, out, _ = Popen_safe(comp.get_exelist() + ['-print-search-dirs'])
          if p.returncode != 0:
              raise mesonlib.MesonException('Could not calculate system search dirs')
          out = out.split('\n')[index].lstrip('libraries: =').split(':')
          return [os.path.normpath(p) for p in out]
  
      def get_exe_wrapper(self):
          if not self.cross_info.need_exe_wrapper():
              from .dependencies import EmptyExternalProgram
              return EmptyExternalProgram()
          return self.exe_wrapper
  
  
  class CrossBuildInfo:
      def __init__(self, filename):
          self.config = {'properties': {}}
          self.parse_datafile(filename)
          if 'host_machine' not in self.config and 'target_machine' not in self.config:
              raise mesonlib.MesonException('Cross info file must have either host or a target machine.')
          if 'host_machine' in self.config and 'binaries' not in self.config:
              raise mesonlib.MesonException('Cross file with "host_machine" is missing "binaries".')
  
      def ok_type(self, i):
          return isinstance(i, (str, int, bool))
  
      def parse_datafile(self, filename):
          config = configparser.ConfigParser()
          try:
              with open(filename, 'r') as f:
                  config.read_file(f, filename)
          except FileNotFoundError:
              raise EnvironmentException('File not found: %s.' % filename)
          # This is a bit hackish at the moment.
          for s in config.sections():
              self.config[s] = {}
              for entry in config[s]:
                  value = config[s][entry]
                  if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry:
                      raise EnvironmentException('Malformed variable name %s in cross file..' % entry)
                  try:
                      res = eval(value, {'__builtins__': None}, {'true': True, 'false': False})
                  except Exception:
                      raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
  
                  if entry == 'cpu_family' and res not in known_cpu_families:
                      mlog.warning('Unknown CPU family %s, please report this at https://github.com/mesonbuild/meson/issues/new' % value)
  
                  if self.ok_type(res):
                      self.config[s][entry] = res
                  elif isinstance(res, list):
                      for i in res:
                          if not self.ok_type(i):
                              raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
                      self.config[s][entry] = res
                  else:
                      raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
  
      def has_host(self):
          return 'host_machine' in self.config
  
      def has_target(self):
          return 'target_machine' in self.config
  
      def has_stdlib(self, language):
          return language + '_stdlib' in self.config['properties']
  
      def get_stdlib(self, language):
          return self.config['properties'][language + '_stdlib']
  
      def get_host_system(self):
          "Name of host system like 'linux', or None"
          if self.has_host():
              return self.config['host_machine']['system']
          return None
  
      def get_properties(self):
          return self.config['properties']
  
      def get_root(self):
          return self.get_properties().get('root', None)
  
      def get_sys_root(self):
          return self.get_properties().get('sys_root', None)
  
      # When compiling a cross compiler we use the native compiler for everything.
      # But not when cross compiling a cross compiler.
      def need_cross_compiler(self):
          return 'host_machine' in self.config
  
      def need_exe_wrapper(self):
          value = self.config['properties'].get('needs_exe_wrapper', None)
          if value is not None:
              return value
          # Can almost always run 32-bit binaries on 64-bit natively if the host
          # and build systems are the same. We don't pass any compilers to
          # detect_cpu_family() here because we always want to know the OS
          # architecture, not what the compiler environment tells us.
          if self.has_host() and detect_cpu_family({}) == 'x86_64' and \
             self.config['host_machine']['cpu_family'] == 'x86' and \
             self.config['host_machine']['system'] == detect_system():
              return False
          return True
  
  
  class MachineInfo:
      def __init__(self, system, cpu_family, cpu, endian):
          self.system = system
          self.cpu_family = cpu_family
          self.cpu = cpu
          self.endian = endian
  
files: mesonbuild/environment.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 configparser, os, platform, re, shlex, shutil, subprocess
  
  from . import coredata
  from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker
  from . import mesonlib
  from .mesonlib import EnvironmentException, Popen_safe
  from . import mlog
  
  from . import compilers
  from .compilers import (
      CLANG_OSX,
      CLANG_STANDARD,
      CLANG_WIN,
      GCC_CYGWIN,
      GCC_MINGW,
      GCC_OSX,
      GCC_STANDARD,
      ICC_STANDARD,
      is_assembly,
      is_header,
      is_library,
      is_llvm_ir,
      is_object,
      is_source,
  )
  from .compilers import (
      ArmCCompiler,
      ArmCPPCompiler,
      ArmclangCCompiler,
      ArmclangCPPCompiler,
      ClangCCompiler,
      ClangCPPCompiler,
      ClangObjCCompiler,
      ClangObjCPPCompiler,
      G95FortranCompiler,
      GnuCCompiler,
      GnuCPPCompiler,
      GnuFortranCompiler,
      GnuObjCCompiler,
      GnuObjCPPCompiler,
      ElbrusCCompiler,
      ElbrusCPPCompiler,
      ElbrusFortranCompiler,
      IntelCCompiler,
      IntelCPPCompiler,
      IntelFortranCompiler,
      JavaCompiler,
      MonoCompiler,
      VisualStudioCsCompiler,
      NAGFortranCompiler,
      Open64FortranCompiler,
      PathScaleFortranCompiler,
      PGIFortranCompiler,
      RustCompiler,
      SunFortranCompiler,
      ValaCompiler,
      VisualStudioCCompiler,
      VisualStudioCPPCompiler,
  )
  
  build_filename = 'meson.build'
  
  known_cpu_families = (
      'aarch64',
      'arm',
      'e2k',
      'ia64',
      'mips',
      'mips64',
      'parisc',
      'ppc',
      'ppc64',
      'riscv32',
      'riscv64',
      's390x',
      'sparc',
      'sparc64',
      'x86',
      'x86_64'
  )
  
  def detect_gcovr(version='3.1', log=False):
      gcovr_exe = 'gcovr'
      try:
          p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
      except (FileNotFoundError, PermissionError):
          # Doesn't exist in PATH or isn't executable
          return None, None
      found = search_version(found)
      if p.returncode == 0:
          if log:
              mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe))))
          return gcovr_exe, mesonlib.version_compare(found, '>=' + version)
      return None, None
  
  def find_coverage_tools():
      gcovr_exe, gcovr_new_rootdir = detect_gcovr()
  
      lcov_exe = 'lcov'
      genhtml_exe = 'genhtml'
  
      if not mesonlib.exe_exists([lcov_exe, '--version']):
          lcov_exe = None
      if not mesonlib.exe_exists([genhtml_exe, '--version']):
          genhtml_exe = None
  
      return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe
  
  def detect_ninja(version='1.5', log=False):
      for n in ['ninja', 'ninja-build']:
          try:
              p, found = Popen_safe([n, '--version'])[0:2]
          except (FileNotFoundError, PermissionError):
              # Doesn't exist in PATH or isn't executable
              continue
          found = found.strip()
          # Perhaps we should add a way for the caller to know the failure mode
          # (not found or too old)
          if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
              if log:
                  mlog.log('Found ninja-{} at {}'.format(found, shlex.quote(shutil.which(n))))
              return n
  
  def detect_native_windows_arch():
      """
      The architecture of Windows itself: x86 or amd64
      """
      # These env variables are always available. See:
      # https://msdn.microsoft.com/en-us/library/aa384274(VS.85).aspx
      # https://blogs.msdn.microsoft.com/david.wang/2006/03/27/howto-detect-process-bitness/
      arch = os.environ.get('PROCESSOR_ARCHITEW6432', '').lower()
      if not arch:
          try:
              # If this doesn't exist, something is messing with the environment
              arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()
          except KeyError:
              raise EnvironmentException('Unable to detect native OS architecture')
      return arch
  
  def detect_windows_arch(compilers):
      """
      Detecting the 'native' architecture of Windows is not a trivial task. We
      cannot trust that the architecture that Python is built for is the 'native'
      one because you can run 32-bit apps on 64-bit Windows using WOW64 and
      people sometimes install 32-bit Python on 64-bit Windows.
  
      We also can't rely on the architecture of the OS itself, since it's
      perfectly normal to compile and run 32-bit applications on Windows as if
      they were native applications. It's a terrible experience to require the
      user to supply a cross-info file to compile 32-bit applications on 64-bit
      Windows. Thankfully, the only way to compile things with Visual Studio on
      Windows is by entering the 'msvc toolchain' environment, which can be
      easily detected.
  
      In the end, the sanest method is as follows:
      1. Check if we're in an MSVC toolchain environment, and if so, return the
         MSVC toolchain architecture as our 'native' architecture.
      2. If not, check environment variables that are set by Windows and WOW64 to
         find out the architecture that Windows is built for, and use that as our
         'native' architecture.
      """
      os_arch = detect_native_windows_arch()
      if os_arch != 'amd64':
          return os_arch
      # If we're on 64-bit Windows, 32-bit apps can be compiled without
      # cross-compilation. So if we're doing that, just set the native arch as
      # 32-bit and pretend like we're running under WOW64. Else, return the
      # actual Windows architecture that we deduced above.
      for compiler in compilers.values():
          # Check if we're using and inside an MSVC toolchain environment
          if compiler.id == 'msvc' and 'VCINSTALLDIR' in os.environ:
              if float(compiler.get_toolset_version()) < 10.0:
                  # On MSVC 2008 and earlier, check 'BUILD_PLAT', where
                  # 'Win32' means 'x86'
                  platform = os.environ.get('BUILD_PLAT', 'x86')
                  if platform == 'Win32':
                      return 'x86'
              else:
                  # On MSVC 2010 and later 'Platform' is only set when the
                  # target arch is not 'x86'.  It's 'x64' when targeting
                  # x86_64 and 'arm' when targeting ARM.
                  platform = os.environ.get('Platform', 'x86').lower()
              if platform == 'x86':
                  return platform
          if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'):
              return 'x86'
      return os_arch
  
  def detect_cpu_family(compilers):
      """
      Python is inconsistent in its platform module.
      It returns different values for the same cpu.
      For x86 it might return 'x86', 'i686' or somesuch.
      Do some canonicalization.
      """
      if mesonlib.is_windows():
          trial = detect_windows_arch(compilers)
      else:
          trial = platform.machine().lower()
      if trial.startswith('i') and trial.endswith('86'):
          return 'x86'
      if trial.startswith('arm'):
          return 'arm'
      if trial.startswith('ppc64'):
          return 'ppc64'
      if trial in ('amd64', 'x64'):
          trial = 'x86_64'
      if trial == 'x86_64':
          # On Linux (and maybe others) there can be any mixture of 32/64 bit
          # code in the kernel, Python, system etc. The only reliable way
          # to know is to check the compiler defines.
          for c in compilers.values():
              try:
                  if c.has_builtin_define('__i386__'):
                      return 'x86'
              except mesonlib.MesonException:
                  # Ignore compilers that do not support has_builtin_define.
                  pass
          return 'x86_64'
      # Add fixes here as bugs are reported.
  
      if trial not in known_cpu_families:
          mlog.warning('Unknown CPU family {!r}, please report this at '
                       'https://github.com/mesonbuild/meson/issues/new with the'
                       'output of `uname -a` and `cat /proc/cpuinfo`'.format(trial))
  
      return trial
  
  def detect_cpu(compilers):
      if mesonlib.is_windows():
          trial = detect_windows_arch(compilers)
      else:
          trial = platform.machine().lower()
      if trial in ('amd64', 'x64'):
          trial = 'x86_64'
      if trial == 'x86_64':
          # Same check as above for cpu_family
          for c in compilers.values():
              try:
                  if c.has_builtin_define('__i386__'):
                      return 'i686' # All 64 bit cpus have at least this level of x86 support.
              except mesonlib.MesonException:
                  pass
          return 'x86_64'
      if trial == 'e2k':
          # Make more precise CPU detection for Elbrus platform.
          trial = platform.processor().lower()
      # Add fixes here as bugs are reported.
      return trial
  
  def detect_system():
      system = platform.system().lower()
      if system.startswith('cygwin'):
          return 'cygwin'
      return system
  
  def detect_msys2_arch():
      if 'MSYSTEM_CARCH' in os.environ:
          return os.environ['MSYSTEM_CARCH']
      return None
  
  def search_version(text):
      # Usually of the type 4.1.4 but compiler output may contain
      # stuff like this:
      # (Sourcery CodeBench Lite 2014.05-29) 4.8.3 20140320 (prerelease)
      # Limiting major version number to two digits seems to work
      # thus far. When we get to GCC 100, this will break, but
      # if we are still relevant when that happens, it can be
      # considered an achievement in itself.
      #
      # This regex is reaching magic levels. If it ever needs
      # to be updated, do not complexify but convert to something
      # saner instead.
      version_regex = '(?<!(\d|\.))(\d{1,2}(\.\d+)+(-[a-zA-Z0-9]+)?)'
      match = re.search(version_regex, text)
      if match:
          return match.group(0)
      return 'unknown version'
  
  class Environment:
      private_dir = 'meson-private'
      log_dir = 'meson-logs'
  
      def __init__(self, source_dir, build_dir, options):
          self.source_dir = source_dir
          self.build_dir = build_dir
          self.scratch_dir = os.path.join(build_dir, Environment.private_dir)
          self.log_dir = os.path.join(build_dir, Environment.log_dir)
          os.makedirs(self.scratch_dir, exist_ok=True)
          os.makedirs(self.log_dir, exist_ok=True)
          try:
              self.coredata = coredata.load(self.get_build_dir())
              self.first_invocation = False
          except FileNotFoundError:
              # WARNING: Don't use any values from coredata in __init__. It gets
              # re-initialized with project options by the interpreter during
              # build file parsing.
              self.coredata = coredata.CoreData(options)
              # Used by the regenchecker script, which runs meson
              self.coredata.meson_command = mesonlib.meson_command
              self.first_invocation = True
          self.cross_info = None
          self.exe_wrapper = None
          if self.coredata.cross_file:
              self.cross_info = CrossBuildInfo(self.coredata.cross_file)
              if 'exe_wrapper' in self.cross_info.config['binaries']:
                  from .dependencies import ExternalProgram
                  self.exe_wrapper = ExternalProgram.from_cross_info(self.cross_info, 'exe_wrapper')
          self.cmd_line_options = options.cmd_line_options.copy()
  
          # List of potential compilers.
          if mesonlib.is_windows():
              self.default_c = ['cl', 'cc', 'gcc', 'clang']
              self.default_cpp = ['cl', 'c++', 'g++', 'clang++']
          else:
              self.default_c = ['cc', 'gcc', 'clang']
              self.default_cpp = ['c++', 'g++', 'clang++']
          if mesonlib.is_windows():
              self.default_cs = ['csc', 'mcs']
          else:
              self.default_cs = ['mcs', 'csc']
          self.default_objc = ['cc']
          self.default_objcpp = ['c++']
          self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort']
          self.default_rust = ['rustc']
          self.default_static_linker = ['ar']
          self.vs_static_linker = ['lib']
          self.gcc_static_linker = ['gcc-ar']
          self.clang_static_linker = ['llvm-ar']
  
          # Various prefixes and suffixes for import libraries, shared libraries,
          # static libraries, and executables.
          # Versioning is added to these names in the backends as-needed.
          cross = self.is_cross_build()
          if mesonlib.for_windows(cross, self):
              self.exe_suffix = 'exe'
              self.object_suffix = 'obj'
              self.win_libdir_layout = True
          elif mesonlib.for_cygwin(cross, self):
              self.exe_suffix = 'exe'
              self.object_suffix = 'o'
              self.win_libdir_layout = True
          else:
              self.exe_suffix = ''
              self.object_suffix = 'o'
              self.win_libdir_layout = False
          if 'STRIP' in os.environ:
              self.native_strip_bin = shlex.split(os.environ['STRIP'])
          else:
              self.native_strip_bin = ['strip']
  
      def is_cross_build(self):
          return self.cross_info is not None
  
      def dump_coredata(self):
          return coredata.save(self.coredata, self.get_build_dir())
  
      def get_script_dir(self):
          import mesonbuild.scripts
          return os.path.dirname(mesonbuild.scripts.__file__)
  
      def get_log_dir(self):
          return self.log_dir
  
      def get_coredata(self):
          return self.coredata
  
      def get_build_command(self, unbuffered=False):
          cmd = mesonlib.meson_command[:]
          if unbuffered and 'python' in cmd[0]:
              cmd.insert(1, '-u')
          return cmd
  
      def is_header(self, fname):
          return is_header(fname)
  
      def is_source(self, fname):
          return is_source(fname)
  
      def is_assembly(self, fname):
          return is_assembly(fname)
  
      def is_llvm_ir(self, fname):
          return is_llvm_ir(fname)
  
      def is_object(self, fname):
          return is_object(fname)
  
      def is_library(self, fname):
          return is_library(fname)
  
      @staticmethod
      def get_gnu_compiler_defines(compiler):
          """
          Detect GNU compiler platform type (Apple, MinGW, Unix)
          """
          # Arguments to output compiler pre-processor defines to stdout
          # gcc, g++, and gfortran all support these arguments
          args = compiler + ['-E', '-dM', '-']
          p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE)
          if p.returncode != 0:
              raise EnvironmentException('Unable to detect GNU compiler type:\n' + output + error)
          # Parse several lines of the type:
          # `#define ___SOME_DEF some_value`
          # and extract `___SOME_DEF`
          defines = {}
          for line in output.split('\n'):
              if not line:
                  continue
              d, *rest = line.split(' ', 2)
              if d != '#define':
                  continue
              if len(rest) == 1:
                  defines[rest] = True
              if len(rest) == 2:
                  defines[rest[0]] = rest[1]
          return defines
  
      @staticmethod
      def get_gnu_version_from_defines(defines):
          dot = '.'
          major = defines.get('__GNUC__', '0')
          minor = defines.get('__GNUC_MINOR__', '0')
          patch = defines.get('__GNUC_PATCHLEVEL__', '0')
          return dot.join((major, minor, patch))
  
      @staticmethod
      def get_lcc_version_from_defines(defines):
          dot = '.'
          generation_and_major = defines.get('__LCC__', '100')
          generation = generation_and_major[:1]
          major = generation_and_major[1:]
          minor = defines.get('__LCC_MINOR__', '0')
          return dot.join((generation, major, minor))
  
      @staticmethod
      def get_gnu_compiler_type(defines):
          # Detect GCC type (Apple, MinGW, Cygwin, Unix)
          if '__APPLE__' in defines:
              return GCC_OSX
          elif '__MINGW32__' in defines or '__MINGW64__' in defines:
              return GCC_MINGW
          elif '__CYGWIN__' in defines:
              return GCC_CYGWIN
          return GCC_STANDARD
  
      def warn_about_lang_pointing_to_cross(self, compiler_exe, evar):
          evar_str = os.environ.get(evar, 'WHO_WOULD_CALL_THEIR_COMPILER_WITH_THIS_NAME')
          if evar_str == compiler_exe:
              mlog.warning('''Env var %s seems to point to the cross compiler.
  This is probably wrong, it should always point to the native compiler.''' % evar)
  
      def _get_compilers(self, lang, evar, want_cross):
          '''
          The list of compilers is detected in the exact same way for
          C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here.
          '''
          if self.is_cross_build() and want_cross:
              if lang not in self.cross_info.config['binaries']:
                  raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang))
              compilers = mesonlib.stringlistify(self.cross_info.config['binaries'][lang])
              # Ensure ccache exists and remove it if it doesn't
              if compilers[0] == 'ccache':
                  compilers = compilers[1:]
                  ccache = self.detect_ccache()
              else:
                  ccache = []
              self.warn_about_lang_pointing_to_cross(compilers[0], evar)
              # Return value has to be a list of compiler 'choices'
              compilers = [compilers]
              is_cross = True
              exe_wrap = self.get_exe_wrapper()
          elif evar in os.environ:
              compilers = shlex.split(os.environ[evar])
              # Ensure ccache exists and remove it if it doesn't
              if compilers[0] == 'ccache':
                  compilers = compilers[1:]
                  ccache = self.detect_ccache()
              else:
                  ccache = []
              # Return value has to be a list of compiler 'choices'
              compilers = [compilers]
              is_cross = False
              exe_wrap = None
          else:
              compilers = getattr(self, 'default_' + lang)
              ccache = self.detect_ccache()
              is_cross = False
              exe_wrap = None
          return compilers, ccache, is_cross, exe_wrap
  
      def _handle_exceptions(self, exceptions, binaries, bintype='compiler'):
          errmsg = 'Unknown {}(s): {}'.format(bintype, binaries)
          if exceptions:
              errmsg += '\nThe follow exceptions were encountered:'
              for (c, e) in exceptions.items():
                  errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
          raise EnvironmentException(errmsg)
  
      def _detect_c_or_cpp_compiler(self, lang, evar, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers(lang, evar, want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              if 'cl' in compiler or 'cl.exe' in compiler:
                  # Watcom C provides it's own cl.exe clone that mimics an older
                  # version of Microsoft's compiler. Since Watcom's cl.exe is
                  # just a wrapper, we skip using it if we detect its presence
                  # so as not to confuse Meson when configuring for MSVC.
                  #
                  # Additionally the help text of Watcom's cl.exe is paged, and
                  # the binary will not exit without human intervention. In
                  # practice, Meson will block waiting for Watcom's cl.exe to
                  # exit, which requires user input and thus will never exit.
                  if 'WATCOM' in os.environ:
                      def sanitize(p):
                          return os.path.normcase(os.path.abspath(p))
  
                      watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')),
                                    sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe'))]
                      found_cl = sanitize(shutil.which('cl'))
                      if found_cl in watcom_cls:
                          continue
                  arg = '/?'
              elif 'armcc' in compiler[0]:
                  arg = '--vsn'
              else:
                  arg = '--version'
              try:
                  p, out, err = Popen_safe(compiler + [arg])
              except OSError as e:
                  popen_exceptions[' '.join(compiler + [arg])] = e
                  continue
              version = search_version(out)
              full_version = out.split('\n', 1)[0]
  
              guess_gcc_or_lcc = False
              if 'Free Software Foundation' in out:
                  guess_gcc_or_lcc = 'gcc'
              if 'e2k' in out and 'lcc' in out:
                  guess_gcc_or_lcc = 'lcc'
  
              if guess_gcc_or_lcc:
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  gtype = self.get_gnu_compiler_type(defines)
                  if guess_gcc_or_lcc == 'lcc':
                      version = self.get_lcc_version_from_defines(defines)
                      cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler
                  else:
                      version = self.get_gnu_version_from_defines(defines)
                      cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
                  return cls(ccache + compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version)
  
              if 'armclang' in out:
                  # The compiler version is not present in the first line of output,
                  # instead it is present in second line, startswith 'Component:'.
                  # So, searching for the 'Component' in out although we know it is
                  # present in second line, as we are not sure about the
                  # output format in future versions
                  arm_ver_str = re.search('.*Component.*', out)
                  if arm_ver_str is None:
                      popen_exceptions[' '.join(compiler)] = 'version string not found'
                      continue
                  arm_ver_str = arm_ver_str.group(0)
                  # Override previous values
                  version = search_version(arm_ver_str)
                  full_version = arm_ver_str
                  cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler
                  return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
              if 'clang' in out:
                  if 'Apple' in out or mesonlib.for_darwin(want_cross, self):
                      cltype = CLANG_OSX
                  elif 'windows' in out or mesonlib.for_windows(want_cross, self):
                      cltype = CLANG_WIN
                  else:
                      cltype = CLANG_STANDARD
                  cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler
                  return cls(ccache + compiler, version, cltype, is_cross, exe_wrap, full_version=full_version)
              if 'Microsoft' in out or 'Microsoft' in err:
                  # Latest versions of Visual Studio print version
                  # number to stderr but earlier ones print version
                  # on stdout.  Why? Lord only knows.
                  # Check both outputs to figure out version.
                  version = search_version(err)
                  if version == 'unknown version':
                      version = search_version(out)
                  if version == 'unknown version':
                      m = 'Failed to detect MSVC compiler arch: stderr was\n{!r}'
                      raise EnvironmentException(m.format(err))
                  is_64 = err.split('\n')[0].endswith(' x64')
                  cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
                  return cls(compiler, version, is_cross, exe_wrap, is_64)
              if '(ICC)' in out:
                  # TODO: add microsoft add check OSX
                  inteltype = ICC_STANDARD
                  cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
                  return cls(ccache + compiler, version, inteltype, is_cross, exe_wrap, full_version=full_version)
              if 'ARM' in out:
                  cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler
                  return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_c_compiler(self, want_cross):
          return self._detect_c_or_cpp_compiler('c', 'CC', want_cross)
  
      def detect_cpp_compiler(self, want_cross):
          return self._detect_c_or_cpp_compiler('cpp', 'CXX', want_cross)
  
      def detect_fortran_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('fortran', 'FC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              for arg in ['--version', '-V']:
                  try:
                      p, out, err = Popen_safe(compiler + [arg])
                  except OSError as e:
                      popen_exceptions[' '.join(compiler + [arg])] = e
                      continue
  
                  version = search_version(out)
                  full_version = out.split('\n', 1)[0]
  
                  guess_gcc_or_lcc = False
                  if 'GNU Fortran' in out:
                      guess_gcc_or_lcc = 'gcc'
                  if 'e2k' in out and 'lcc' in out:
                      guess_gcc_or_lcc = 'lcc'
  
                  if guess_gcc_or_lcc:
                      defines = self.get_gnu_compiler_defines(compiler)
                      if not defines:
                          popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                          continue
                      gtype = self.get_gnu_compiler_type(defines)
                      if guess_gcc_or_lcc == 'lcc':
                          version = self.get_lcc_version_from_defines(defines)
                          cls = ElbrusFortranCompiler
                      else:
                          version = self.get_gnu_version_from_defines(defines)
                          cls = GnuFortranCompiler
                      return cls(compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version)
  
                  if 'G95' in out:
                      return G95FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'Sun Fortran' in err:
                      version = search_version(err)
                      return SunFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'ifort (IFORT)' in out:
                      return IntelFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'PathScale EKOPath(tm)' in err:
                      return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'PGI Compilers' in out:
                      return PGIFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'Open64 Compiler Suite' in err:
                      return Open64FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
  
                  if 'NAG Fortran' in err:
                      return NAGFortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def get_scratch_dir(self):
          return self.scratch_dir
  
      def detect_objc_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('objc', 'OBJC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out, err = Popen_safe(compiler + arg)
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
              version = search_version(out)
              if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  gtype = self.get_gnu_compiler_type(defines)
                  version = self.get_gnu_version_from_defines(defines)
                  return GnuObjCCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
              if out.startswith('Apple LLVM'):
                  return ClangObjCCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
              if out.startswith('clang'):
                  return ClangObjCCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_objcpp_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('objcpp', 'OBJCXX', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out, err = Popen_safe(compiler + arg)
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
              version = search_version(out)
              if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out):
                  defines = self.get_gnu_compiler_defines(compiler)
                  if not defines:
                      popen_exceptions[' '.join(compiler)] = 'no pre-processor defines'
                      continue
                  gtype = self.get_gnu_compiler_type(defines)
                  version = self.get_gnu_version_from_defines(defines)
                  return GnuObjCPPCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
              if out.startswith('Apple LLVM'):
                  return ClangObjCPPCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
              if out.startswith('clang'):
                  return ClangObjCPPCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_java_compiler(self):
          exelist = ['javac']
          try:
              p, out, err = Popen_safe(exelist + ['-version'])
          except OSError:
              raise EnvironmentException('Could not execute Java compiler "%s"' % ' '.join(exelist))
          version = search_version(err)
          if 'javac' in out or 'javac' in err:
              return JavaCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_cs_compiler(self):
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('cs', 'CSC', False)
          popen_exceptions = {}
          for comp in compilers:
              if not isinstance(comp, list):
                  comp = [comp]
              try:
                  p, out, err = Popen_safe(comp + ['--version'])
              except OSError as e:
                  popen_exceptions[' '.join(comp + ['--version'])] = e
                  continue
  
              version = search_version(out)
              if 'Mono' in out:
                  return MonoCompiler(comp, version)
              elif "Visual C#" in out:
                  return VisualStudioCsCompiler(comp, version)
  
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_vala_compiler(self):
          if 'VALAC' in os.environ:
              exelist = shlex.split(os.environ['VALAC'])
          else:
              exelist = ['valac']
          try:
              p, out = Popen_safe(exelist + ['--version'])[0:2]
          except OSError:
              raise EnvironmentException('Could not execute Vala compiler "%s"' % ' '.join(exelist))
          version = search_version(out)
          if 'Vala' in out:
              return ValaCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_rust_compiler(self, want_cross):
          popen_exceptions = {}
          compilers, ccache, is_cross, exe_wrap = self._get_compilers('rust', 'RUSTC', want_cross)
          for compiler in compilers:
              if isinstance(compiler, str):
                  compiler = [compiler]
              arg = ['--version']
              try:
                  p, out = Popen_safe(compiler + arg)[0:2]
              except OSError as e:
                  popen_exceptions[' '.join(compiler + arg)] = e
                  continue
  
              version = search_version(out)
  
              if 'rustc' in out:
                  return RustCompiler(compiler, version, is_cross, exe_wrap)
  
          self._handle_exceptions(popen_exceptions, compilers)
  
      def detect_d_compiler(self, want_cross):
          is_cross = False
          # Search for a D compiler.
          # We prefer LDC over GDC unless overridden with the DC
          # environment variable because LDC has a much more
          # up to date language version at time (2016).
          if 'DC' in os.environ:
              exelist = shlex.split(os.environ['DC'])
              if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')):
                      raise EnvironmentException('Meson doesn\'t support %s as it\'s only a DMD frontend for another compiler. Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.' % exelist[-1])
          elif self.is_cross_build() and want_cross:
              exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d'])
              is_cross = True
          elif shutil.which("ldc2"):
              exelist = ['ldc2']
          elif shutil.which("ldc"):
              exelist = ['ldc']
          elif shutil.which("gdc"):
              exelist = ['gdc']
          elif shutil.which("dmd"):
              exelist = ['dmd']
          else:
              raise EnvironmentException('Could not find any supported D compiler.')
  
          try:
              p, out = Popen_safe(exelist + ['--version'])[0:2]
          except OSError:
              raise EnvironmentException('Could not execute D compiler "%s"' % ' '.join(exelist))
          version = search_version(out)
          full_version = out.split('\n', 1)[0]
  
          # Detect which MSVC build environment is currently active.
          is_64 = False
          c_compiler = {}
          if mesonlib.is_windows() and 'VCINSTALLDIR' in os.environ:
              # MSVC compiler is required for correct platform detection.
              c_compiler = {'c': self.detect_c_compiler(want_cross)}
  
          if detect_cpu_family(c_compiler) == 'x86_64':
              is_64 = True
  
          if 'LLVM D compiler' in out:
              return compilers.LLVMDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          elif 'gdc' in out:
              return compilers.GnuDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          elif 'The D Language Foundation' in out or 'Digital Mars' in out:
              return compilers.DmdDCompiler(exelist, version, is_cross, is_64, full_version=full_version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_swift_compiler(self):
          exelist = ['swiftc']
          try:
              p, _, err = Popen_safe(exelist + ['-v'])
          except OSError:
              raise EnvironmentException('Could not execute Swift compiler "%s"' % ' '.join(exelist))
          version = search_version(err)
          if 'Swift' in err:
              return compilers.SwiftCompiler(exelist, version)
          raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
  
      def detect_static_linker(self, compiler):
          if compiler.is_cross:
              linker = self.cross_info.config['binaries']['ar']
              if isinstance(linker, str):
                  linker = [linker]
              linkers = [linker]
          else:
              evar = 'AR'
              if evar in os.environ:
                  linkers = [shlex.split(os.environ[evar])]
              elif isinstance(compiler, compilers.VisualStudioCCompiler):
                  linkers = [self.vs_static_linker]
              elif isinstance(compiler, compilers.GnuCompiler):
                  # Use gcc-ar if available; needed for LTO
                  linkers = [self.gcc_static_linker, self.default_static_linker]
              elif isinstance(compiler, compilers.ClangCompiler):
                  # Use llvm-ar if available; needed for LTO
                  linkers = [self.clang_static_linker, self.default_static_linker]
              elif isinstance(compiler, compilers.DCompiler):
                  # Prefer static linkers over linkers used by D compilers
                  if mesonlib.is_windows():
                      linkers = [self.vs_static_linker, compiler.get_linker_exelist()]
                  else:
                      linkers = [self.default_static_linker, compiler.get_linker_exelist()]
              else:
                  linkers = [self.default_static_linker]
          popen_exceptions = {}
          for linker in linkers:
              if 'lib' in linker or 'lib.exe' in linker:
                  arg = '/?'
              else:
                  arg = '--version'
              try:
                  p, out, err = Popen_safe(linker + [arg])
              except OSError as e:
                  popen_exceptions[' '.join(linker + [arg])] = e
                  continue
              if '/OUT:' in out or '/OUT:' in err:
                  return VisualStudioLinker(linker)
              if p.returncode == 0 and ('armar' in linker or 'armar.exe' in linker):
                  return ArmarLinker(linker)
              if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if 'LDC - the LLVM D compiler' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if 'GDC' in out and ' based on D ' in out:
                  return DLinker(linker, compiler.is_64, compiler.is_msvc)
              if p.returncode == 0:
                  return ArLinker(linker)
              if p.returncode == 1 and err.startswith('usage'): # OSX
                  return ArLinker(linker)
          self._handle_exceptions(popen_exceptions, linkers, 'linker')
          raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linkers))
  
      def detect_ccache(self):
          try:
              has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          except OSError:
              has_ccache = 1
          if has_ccache == 0:
              cmdlist = ['ccache']
          else:
              cmdlist = []
          return cmdlist
  
      def get_source_dir(self):
          return self.source_dir
  
      def get_build_dir(self):
          return self.build_dir
  
      def get_exe_suffix(self):
          return self.exe_suffix
  
      def get_import_lib_dir(self):
          "Install dir for the import library (library used for linking)"
          return self.get_libdir()
  
      def get_shared_module_dir(self):
          "Install dir for shared modules that are loaded at runtime"
          return self.get_libdir()
  
      def get_shared_lib_dir(self):
          "Install dir for the shared library"
          if self.win_libdir_layout:
              return self.get_bindir()
          return self.get_libdir()
  
      def get_static_lib_dir(self):
          "Install dir for the static library"
          return self.get_libdir()
  
      def get_object_suffix(self):
          return self.object_suffix
  
      def get_prefix(self):
          return self.coredata.get_builtin_option('prefix')
  
      def get_libdir(self):
          return self.coredata.get_builtin_option('libdir')
  
      def get_libexecdir(self):
          return self.coredata.get_builtin_option('libexecdir')
  
      def get_bindir(self):
          return self.coredata.get_builtin_option('bindir')
  
      def get_includedir(self):
          return self.coredata.get_builtin_option('includedir')
  
      def get_mandir(self):
          return self.coredata.get_builtin_option('mandir')
  
      def get_datadir(self):
          return self.coredata.get_builtin_option('datadir')
  
      def get_compiler_system_dirs(self):
          for comp in self.coredata.compilers.values():
              if isinstance(comp, compilers.ClangCompiler):
                  index = 1
                  break
              elif isinstance(comp, compilers.GnuCompiler):
                  index = 2
                  break
          else:
              # This option is only supported by gcc and clang. If we don't get a
              # GCC or Clang compiler return and empty list.
              return []
  
          p, out, _ = Popen_safe(comp.get_exelist() + ['-print-search-dirs'])
          if p.returncode != 0:
              raise mesonlib.MesonException('Could not calculate system search dirs')
          out = out.split('\n')[index].lstrip('libraries: =').split(':')
          return [os.path.normpath(p) for p in out]
  
      def get_exe_wrapper(self):
          if not self.cross_info.need_exe_wrapper():
              from .dependencies import EmptyExternalProgram
              return EmptyExternalProgram()
          return self.exe_wrapper
  
  
  class CrossBuildInfo:
      def __init__(self, filename):
          self.config = {'properties': {}}
          self.parse_datafile(filename)
          if 'host_machine' not in self.config and 'target_machine' not in self.config:
              raise mesonlib.MesonException('Cross info file must have either host or a target machine.')
          if 'host_machine' in self.config and 'binaries' not in self.config:
              raise mesonlib.MesonException('Cross file with "host_machine" is missing "binaries".')
  
      def ok_type(self, i):
          return isinstance(i, (str, int, bool))
  
      def parse_datafile(self, filename):
          config = configparser.ConfigParser()
          try:
              with open(filename, 'r') as f:
                  config.read_file(f, filename)
          except FileNotFoundError:
              raise EnvironmentException('File not found: %s.' % filename)
          # This is a bit hackish at the moment.
          for s in config.sections():
              self.config[s] = {}
              for entry in config[s]:
                  value = config[s][entry]
                  if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry:
                      raise EnvironmentException('Malformed variable name %s in cross file..' % entry)
                  try:
                      res = eval(value, {'__builtins__': None}, {'true': True, 'false': False})
                  except Exception:
                      raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
  
                  if entry == 'cpu_family' and res not in known_cpu_families:
                      mlog.warning('Unknown CPU family %s, please report this at https://github.com/mesonbuild/meson/issues/new' % value)
  
                  if self.ok_type(res):
                      self.config[s][entry] = res
                  elif isinstance(res, list):
                      for i in res:
                          if not self.ok_type(i):
                              raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
                      self.config[s][entry] = res
                  else:
                      raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
  
      def has_host(self):
          return 'host_machine' in self.config
  
      def has_target(self):
          return 'target_machine' in self.config
  
      def has_stdlib(self, language):
          return language + '_stdlib' in self.config['properties']
  
      def get_stdlib(self, language):
          return self.config['properties'][language + '_stdlib']
  
      def get_host_system(self):
          "Name of host system like 'linux', or None"
          if self.has_host():
              return self.config['host_machine']['system']
          return None
  
      def get_properties(self):
          return self.config['properties']
  
      def get_root(self):
          return self.get_properties().get('root', None)
  
      def get_sys_root(self):
          return self.get_properties().get('sys_root', None)
  
      # When compiling a cross compiler we use the native compiler for everything.
      # But not when cross compiling a cross compiler.
      def need_cross_compiler(self):
          return 'host_machine' in self.config
  
      def need_exe_wrapper(self):
          value = self.config['properties'].get('needs_exe_wrapper', None)
          if value is not None:
              return value
          # Can almost always run 32-bit binaries on 64-bit natively if the host
          # and build systems are the same. We don't pass any compilers to
          # detect_cpu_family() here because we always want to know the OS
          # architecture, not what the compiler environment tells us.
          if self.has_host() and detect_cpu_family({}) == 'x86_64' and \
             self.config['host_machine']['cpu_family'] == 'x86' and \
             self.config['host_machine']['system'] == detect_system():
              return False
          return True
  
  
  class MachineInfo:
      def __init__(self, system, cpu_family, cpu, endian):
          self.system = system
          self.cpu_family = cpu_family
          self.cpu = cpu
          self.endian = endian
  
files: mesonbuild/environment.py
files: #!/usr/bin/env python3
  # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import stat
  import shlex
  import subprocess
  import re, json
  import tempfile
  import textwrap
  import os
  import shutil
  import unittest
  import platform
  from itertools import chain
  from unittest import mock
  from configparser import ConfigParser
  from glob import glob
  from pathlib import (PurePath, Path)
  
  import mesonbuild.mlog
  import mesonbuild.compilers
  import mesonbuild.environment
  import mesonbuild.mesonlib
  import mesonbuild.coredata
  import mesonbuild.modules.gnome
  from mesonbuild.interpreter import Interpreter, ObjectHolder
  from mesonbuild.mesonlib import (
      is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd,
      windows_proof_rmtree, python_command, version_compare,
      BuildDirLock, Version
  )
  from mesonbuild.environment import detect_ninja
  from mesonbuild.mesonlib import MesonException, EnvironmentException
  from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
  import mesonbuild.modules.pkgconfig
  
  from run_tests import exe_suffix, get_fake_env, get_meson_script
  from run_tests import get_builddir_target_args, get_backend_commands, Backend
  from run_tests import ensure_backend_detects_changes, run_configure_inprocess
  from run_tests import run_mtest_inprocess
  from run_tests import FakeBuild, FakeCompilerOptions
  
  def get_dynamic_section_entry(fname, entry):
      if is_cygwin() or is_osx():
              raise unittest.SkipTest('Test only applicable to ELF platforms')
  
      try:
          raw_out = subprocess.check_output(['readelf', '-d', fname],
                                            universal_newlines=True)
      except FileNotFoundError:
          # FIXME: Try using depfixer.py:Elf() as a fallback
          raise unittest.SkipTest('readelf not found')
      pattern = re.compile(entry + r': \[(.*?)\]')
      for line in raw_out.split('\n'):
          m = pattern.search(line)
          if m is not None:
              return m.group(1)
      return None # The file did not contain the specified entry.
  
  def get_soname(fname):
      return get_dynamic_section_entry(fname, 'soname')
  
  def get_rpath(fname):
      return get_dynamic_section_entry(fname, r'(?:rpath|runpath)')
  
  def is_tarball():
      if not os.path.isdir('docs'):
          return True
      return False
  
  def is_ci():
      if 'TRAVIS' in os.environ or 'APPVEYOR' in os.environ:
          return True
      return False
  
  def _git_init(project_dir):
      subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL)
      subprocess.check_call(['git', 'config',
                             'user.name', 'Author Person'], cwd=project_dir)
      subprocess.check_call(['git', 'config',
                             'user.email', 'teh_coderz@example.com'], cwd=project_dir)
      subprocess.check_call('git add *', cwd=project_dir, shell=True,
                            stdout=subprocess.DEVNULL)
      subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir,
                            stdout=subprocess.DEVNULL)
  
  def skipIfNoPkgconfig(f):
      '''
      Skip this test if no pkg-config is found, unless we're on Travis or
      Appveyor CI.  This allows users to run our test suite without having
      pkg-config installed on, f.ex., macOS, while ensuring that our CI does not
      silently skip the test because of misconfiguration.
  
      Note: Yes, we provide pkg-config even while running Windows CI
      '''
      def wrapped(*args, **kwargs):
          if not is_ci() and shutil.which('pkg-config') is None:
              raise unittest.SkipTest('pkg-config not found')
          return f(*args, **kwargs)
      return wrapped
  
  class PatchModule:
      '''
      Fancy monkey-patching! Whee! Can't use mock.patch because it only
      patches in the local namespace.
      '''
      def __init__(self, func, name, impl):
          self.func = func
          assert(isinstance(name, str))
          self.func_name = name
          self.old_impl = None
          self.new_impl = impl
  
      def __enter__(self):
          self.old_impl = self.func
          exec('{} = self.new_impl'.format(self.func_name))
  
      def __exit__(self, *args):
          exec('{} = self.old_impl'.format(self.func_name))
  
  
  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
          cc = mesonbuild.compilers.CCompiler([], 'fake', False)
          # Test that bad initialization fails
          self.assertRaises(TypeError, cargsfunc, [])
          self.assertRaises(TypeError, cargsfunc, [], [])
          self.assertRaises(TypeError, cargsfunc, cc, [], [])
          # Test that empty initialization works
          a = cargsfunc(cc)
          self.assertEqual(a, [])
          # Test that list initialization works
          a = cargsfunc(['-I.', '-I..'], cc)
          self.assertEqual(a, ['-I.', '-I..'])
          # Test that there is no de-dup on initialization
          self.assertEqual(cargsfunc(['-I.', '-I.'], cc), ['-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 yields 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'])
          # Test that adding to a list with old args does nothing
          a = ['-Ibar', '-Ibaz', '-Ifoo'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
  
          ## Test that adding libraries works
          l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo'])
          # Adding a library and a libpath appends both correctly
          l += ['-Lbardir', '-lbar']
          self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
          # Adding the same library again does nothing
          l += ['-lbar']
          self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
  
          ## Test that 'direct' append and extend works
          l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo'])
          # Direct-adding a library and a libpath appends both correctly
          l.extend_direct(['-Lbardir', '-lbar'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar'])
          # Direct-adding the same library again still adds it
          l.append_direct('-lbar')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar'])
          # Direct-adding with absolute path deduplicates
          l.append_direct('/libbaz.a')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
          # Adding libbaz again does nothing
          l.append_direct('/libbaz.a')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
  
      def test_compiler_args_class_gnuld(self):
          cargsfunc = mesonbuild.compilers.CompilerArgs
          ## Test --start/end-group
          gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', mesonbuild.compilers.CompilerType.GCC_STANDARD, False)
          ## Test that 'direct' append and extend works
          l = cargsfunc(gcc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
          # Direct-adding a library and a libpath appends both correctly
          l.extend_direct(['-Lbardir', '-lbar'])
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group'])
          # Direct-adding the same library again still adds it
          l.append_direct('-lbar')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group'])
          # Direct-adding with absolute path deduplicates
          l.append_direct('/libbaz.a')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
          # Adding libbaz again does nothing
          l.append_direct('/libbaz.a')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
          # Adding a non-library argument doesn't include it in the group
          l += ['-Lfoo', '-Wl,--export-dynamic']
          self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic'])
          # -Wl,-lfoo is detected as a library and gets added to the group
          l.append('-Wl,-ldl')
          self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group'])
  
      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']
          self.assertRaises(ME, substfunc, cmd, d)
          # 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']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
          # Two inputs, two outputs
          outputs = ['dir/out.c', 'dir/out2.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
               '@OUTDIR@': 'dir'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@OUTPUT@', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
          cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
          cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
          self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
          # Many inputs, can't use @INPUT@ like this
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Many outputs, can't use @OUTPUT@ like this
          cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
      def test_needs_exe_wrapper_override(self):
          config = ConfigParser()
          config['binaries'] = {
              'c': '\'/usr/bin/gcc\'',
          }
          config['host_machine'] = {
              'system': '\'linux\'',
              'cpu_family': '\'arm\'',
              'cpu': '\'armv7\'',
              'endian': '\'little\'',
          }
          # Can not be used as context manager because we need to
          # open it a second time and this is not possible on
          # Windows.
          configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
          configfilename = configfile.name
          config.write(configfile)
          configfile.flush()
          configfile.close()
          detected_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper()
          os.unlink(configfilename)
  
          desired_value = not detected_value
          config['properties'] = {
              'needs_exe_wrapper': 'true' if desired_value else 'false'
          }
  
          configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
          configfilename = configfile.name
          config.write(configfile)
          configfile.close()
          forced_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper()
          os.unlink(configfilename)
  
          self.assertEqual(forced_value, desired_value)
  
      def test_listify(self):
          listify = mesonbuild.mesonlib.listify
          # Test sanity
          self.assertEqual([1], listify(1))
          self.assertEqual([], listify([]))
          self.assertEqual([1], listify([1]))
          # Test flattening
          self.assertEqual([1, 2, 3], listify([1, [2, 3]]))
          self.assertEqual([1, 2, 3], listify([1, [2, [3]]]))
          self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
          # Test flattening and unholdering
          holder1 = ObjectHolder(1)
          holder3 = ObjectHolder(3)
          self.assertEqual([holder1], listify(holder1))
          self.assertEqual([holder1], listify([holder1]))
          self.assertEqual([holder1, 2], listify([holder1, 2]))
          self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]]))
          self.assertEqual([1], listify(holder1, unholder=True))
          self.assertEqual([1], listify([holder1], unholder=True))
          self.assertEqual([1, 2], listify([holder1, 2], unholder=True))
          self.assertEqual([1, 2, 3], listify([holder1, 2, [holder3]], unholder=True))
          # Unholding doesn't work recursively when not flattening
          self.assertEqual([1, [2], [holder3]], listify([holder1, [2], [holder3]], unholder=True, flatten=False))
  
      def test_extract_as_list(self):
          extract = mesonbuild.mesonlib.extract_as_list
          # Test sanity
          kwargs = {'sources': [1, 2, 3]}
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
          self.assertEqual(kwargs, {'sources': [1, 2, 3]})
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True))
          self.assertEqual(kwargs, {})
          # Test unholding
          holder3 = ObjectHolder(3)
          kwargs = {'sources': [1, 2, holder3]}
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True))
          self.assertEqual(kwargs, {'sources': [1, 2, holder3]})
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True, pop=True))
          self.assertEqual(kwargs, {})
          # Test listification
          kwargs = {'sources': [1, 2, 3], 'pch_sources': [4, 5, 6]}
          self.assertEqual([[1, 2, 3], [4, 5, 6]], extract(kwargs, 'sources', 'pch_sources'))
  
      def test_pkgconfig_module(self):
  
          class Mock:
              pass
  
          mock = Mock()
          mock.pcdep = Mock()
          mock.pcdep.name = "some_name"
          mock.version_reqs = []
  
          # pkgconfig dependency as lib
          deps = mesonbuild.modules.pkgconfig.DependenciesHelper("thislib")
          deps.add_pub_libs([mock])
          self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
  
          # pkgconfig dependency as requires
          deps = mesonbuild.modules.pkgconfig.DependenciesHelper("thislib")
          deps.add_pub_reqs([mock])
          self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
  
      def _test_all_naming(self, cc, env, patterns, platform):
          shr = patterns[platform]['shared']
          stc = patterns[platform]['static']
          p = cc.get_library_naming(env, 'shared')
          self.assertEqual(p, shr)
          p = cc.get_library_naming(env, 'static')
          self.assertEqual(p, stc)
          p = cc.get_library_naming(env, 'static-shared')
          self.assertEqual(p, stc + shr)
          p = cc.get_library_naming(env, 'shared-static')
          self.assertEqual(p, shr + stc)
          p = cc.get_library_naming(env, 'default')
          self.assertEqual(p, shr + stc)
          # Test find library by mocking up openbsd
          if platform != 'openbsd':
              return
          with tempfile.TemporaryDirectory() as tmpdir:
              with open(os.path.join(tmpdir, 'libfoo.so.6.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.5.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.54.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.66a.0b'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.70.0.so.1'), 'w') as f:
                  f.write('')
              found = cc.find_library_real('foo', env, [tmpdir], '', 'default')
              self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
  
      def test_find_library_patterns(self):
          '''
          Unit test for the library search patterns used by find_library()
          '''
          unix_static = ('lib{}.a', '{}.a')
          msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib')
          # This is the priority list of pattern matching for library searching
          patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*'),
                                  'static': unix_static},
                      'linux': {'shared': ('lib{}.so', '{}.so'),
                                'static': unix_static},
                      'darwin': {'shared': ('lib{}.dylib', '{}.dylib'),
                                 'static': unix_static},
                      'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll',
                                            'lib{}.dll.a', '{}.dll', '{}.dll.a'),
                                 'static': ('cyg{}.a',) + unix_static},
                      'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'),
                                       'static': msvc_static},
                      'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll',
                                                   '{}.dll.a', '{}.lib', '{}.dll'),
                                        'static': msvc_static}}
          env = get_fake_env('', '', '')
          cc = env.detect_c_compiler(False)
          if is_osx():
              self._test_all_naming(cc, env, patterns, 'darwin')
          elif is_cygwin():
              self._test_all_naming(cc, env, patterns, 'cygwin')
          elif is_windows():
              if cc.get_id() == 'msvc':
                  self._test_all_naming(cc, env, patterns, 'windows-msvc')
              else:
                  self._test_all_naming(cc, env, patterns, 'windows-mingw')
          else:
              self._test_all_naming(cc, env, patterns, 'linux')
              # Mock OpenBSD since we don't have tests for it
              true = lambda x, y: True
              if not is_openbsd():
                  with PatchModule(mesonbuild.compilers.c.for_openbsd,
                                   'mesonbuild.compilers.c.for_openbsd', true):
                      self._test_all_naming(cc, env, patterns, 'openbsd')
              else:
                  self._test_all_naming(cc, env, patterns, 'openbsd')
              with PatchModule(mesonbuild.compilers.c.for_darwin,
                               'mesonbuild.compilers.c.for_darwin', true):
                  self._test_all_naming(cc, env, patterns, 'darwin')
              with PatchModule(mesonbuild.compilers.c.for_cygwin,
                               'mesonbuild.compilers.c.for_cygwin', true):
                  self._test_all_naming(cc, env, patterns, 'cygwin')
              with PatchModule(mesonbuild.compilers.c.for_windows,
                               'mesonbuild.compilers.c.for_windows', true):
                  self._test_all_naming(cc, env, patterns, 'windows-mingw')
              cc.id = 'msvc'
              with PatchModule(mesonbuild.compilers.c.for_windows,
                               'mesonbuild.compilers.c.for_windows', true):
                  self._test_all_naming(cc, env, patterns, 'windows-msvc')
  
      def test_pkgconfig_parse_libs(self):
          '''
          Unit test for parsing of pkg-config output to search for libraries
  
          https://github.com/mesonbuild/meson/issues/3951
          '''
          with tempfile.TemporaryDirectory() as tmpdir:
              pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
              env = get_fake_env('', '', '')
              compiler = env.detect_c_compiler(False)
              env.coredata.compilers = {'c': compiler}
              env.coredata.compiler_options['c_link_args'] = FakeCompilerOptions()
              p1 = Path(tmpdir) / '1'
              p2 = Path(tmpdir) / '2'
              p1.mkdir()
              p2.mkdir()
              # libfoo.a is in one prefix
              (p1 / 'libfoo.a').open('w').close()
              # libbar.a is in both prefixes
              (p1 / 'libbar.a').open('w').close()
              (p2 / 'libbar.a').open('w').close()
              # Ensure that we never statically link to these
              (p1 / 'libpthread.a').open('w').close()
              (p1 / 'libm.a').open('w').close()
              (p1 / 'libc.a').open('w').close()
              (p1 / 'libdl.a').open('w').close()
              (p1 / 'librt.a').open('w').close()
  
              def fake_call_pkgbin(self, args, env=None):
                  if '--libs' not in args:
                      return 0, ''
                  if args[0] == 'foo':
                      return 0, '-L{} -lfoo -L{} -lbar'.format(p2.as_posix(), p1.as_posix())
                  if args[0] == 'bar':
                      return 0, '-L{} -lbar'.format(p2.as_posix())
                  if args[0] == 'internal':
                      return 0, '-L{} -lpthread -lm -lc -lrt -ldl'.format(p1.as_posix())
  
              old_call = PkgConfigDependency._call_pkgbin
              old_check = PkgConfigDependency.check_pkgconfig
              PkgConfigDependency._call_pkgbin = fake_call_pkgbin
              PkgConfigDependency.check_pkgconfig = lambda x: pkgbin
              # Test begins
              kwargs = {'required': True, 'silent': True}
              foo_dep = PkgConfigDependency('foo', env, kwargs)
              self.assertEqual(foo_dep.get_link_args(),
                               [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
              bar_dep = PkgConfigDependency('bar', env, kwargs)
              self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
              internal_dep = PkgConfigDependency('internal', env, kwargs)
              if compiler.get_id() == 'msvc':
                  self.assertEqual(internal_dep.get_link_args(), [])
              else:
                  link_args = internal_dep.get_link_args()
                  for link_arg in link_args:
                      for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
                          self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args)
              # Test ends
              PkgConfigDependency._call_pkgbin = old_call
              PkgConfigDependency.check_pkgconfig = old_check
              # Reset dependency class to ensure that in-process configure doesn't mess up
              PkgConfigDependency.pkgbin_cache = {}
              PkgConfigDependency.class_pkgbin = None
  
      def test_version_compare(self):
          comparefunc = mesonbuild.mesonlib.version_compare_many
          for (a, b, result) in [
                  ('0.99.beta19', '>= 0.99.beta14', True),
          ]:
              self.assertEqual(comparefunc(a, b)[0], result)
  
          for (a, b, result) in [
                  # examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison
                  ("1.0010", "1.9", 1),
                  ("1.05", "1.5", 0),
                  ("1.0", "1", 1),
                  ("2.50", "2.5", 1),
                  ("fc4", "fc.4", 0),
                  ("FC5", "fc4", -1),
                  ("2a", "2.0", -1),
                  ("1.0", "1.fc4", 1),
                  ("3.0.0_fc", "3.0.0.fc", 0),
                  # from RPM tests
                  ("1.0", "1.0", 0),
                  ("1.0", "2.0", -1),
                  ("2.0", "1.0", 1),
                  ("2.0.1", "2.0.1", 0),
                  ("2.0", "2.0.1", -1),
                  ("2.0.1", "2.0", 1),
                  ("2.0.1a", "2.0.1a", 0),
                  ("2.0.1a", "2.0.1", 1),
                  ("2.0.1", "2.0.1a", -1),
                  ("5.5p1", "5.5p1", 0),
                  ("5.5p1", "5.5p2", -1),
                  ("5.5p2", "5.5p1", 1),
                  ("5.5p10", "5.5p10", 0),
                  ("5.5p1", "5.5p10", -1),
                  ("5.5p10", "5.5p1", 1),
                  ("10xyz", "10.1xyz", -1),
                  ("10.1xyz", "10xyz", 1),
                  ("xyz10", "xyz10", 0),
                  ("xyz10", "xyz10.1", -1),
                  ("xyz10.1", "xyz10", 1),
                  ("xyz.4", "xyz.4", 0),
                  ("xyz.4", "8", -1),
                  ("8", "xyz.4", 1),
                  ("xyz.4", "2", -1),
                  ("2", "xyz.4", 1),
                  ("5.5p2", "5.6p1", -1),
                  ("5.6p1", "5.5p2", 1),
                  ("5.6p1", "6.5p1", -1),
                  ("6.5p1", "5.6p1", 1),
                  ("6.0.rc1", "6.0", 1),
                  ("6.0", "6.0.rc1", -1),
                  ("10b2", "10a1", 1),
                  ("10a2", "10b2", -1),
                  ("1.0aa", "1.0aa", 0),
                  ("1.0a", "1.0aa", -1),
                  ("1.0aa", "1.0a", 1),
                  ("10.0001", "10.0001", 0),
                  ("10.0001", "10.1", 0),
                  ("10.1", "10.0001", 0),
                  ("10.0001", "10.0039", -1),
                  ("10.0039", "10.0001", 1),
                  ("4.999.9", "5.0", -1),
                  ("5.0", "4.999.9", 1),
                  ("20101121", "20101121", 0),
                  ("20101121", "20101122", -1),
                  ("20101122", "20101121", 1),
                  ("2_0", "2_0", 0),
                  ("2.0", "2_0", 0),
                  ("2_0", "2.0", 0),
                  ("a", "a", 0),
                  ("a+", "a+", 0),
                  ("a+", "a_", 0),
                  ("a_", "a+", 0),
                  ("+a", "+a", 0),
                  ("+a", "_a", 0),
                  ("_a", "+a", 0),
                  ("+_", "+_", 0),
                  ("_+", "+_", 0),
                  ("_+", "_+", 0),
                  ("+", "_", 0),
                  ("_", "+", 0),
                  # other tests
                  ('0.99.beta19', '0.99.beta14', 1),
                  ("1.0.0", "2.0.0", -1),
                  (".0.0", "2.0.0", -1),
                  ("alpha", "beta", -1),
                  ("1.0", "1.0.0", -1),
                  ("2.456", "2.1000", -1),
                  ("2.1000", "3.111", -1),
                  ("2.001", "2.1", 0),
                  ("2.34", "2.34", 0),
                  ("6.1.2", "6.3.8", -1),
                  ("1.7.3.0", "2.0.0", -1),
                  ("2.24.51", "2.25", -1),
                  ("2.1.5+20120813+gitdcbe778", "2.1.5", 1),
                  ("3.4.1", "3.4b1", 1),
                  ("041206", "200090325", -1),
                  ("0.6.2+git20130413", "0.6.2", 1),
                  ("2.6.0+bzr6602", "2.6.0", 1),
                  ("2.6.0", "2.6b2", 1),
                  ("2.6.0+bzr6602", "2.6b2x", 1),
                  ("0.6.7+20150214+git3a710f9", "0.6.7", 1),
                  ("15.8b", "15.8.0.1", -1),
                  ("1.2rc1", "1.2.0", -1),
          ]:
              ver_a = Version(a)
              ver_b = Version(b)
              self.assertEqual(ver_a.__cmp__(ver_b), result)
              self.assertEqual(ver_b.__cmp__(ver_a), -result)
  
  @unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
  class DataTests(unittest.TestCase):
  
      def test_snippets(self):
          hashcounter = re.compile('^ *(#)+')
          snippet_dir = Path('docs/markdown/snippets')
          self.assertTrue(snippet_dir.is_dir())
          for f in snippet_dir.glob('*'):
              self.assertTrue(f.is_file())
              if f.parts[-1].endswith('~'):
                  continue
              if f.suffix == '.md':
                  in_code_block = False
                  with f.open() as snippet:
                      for line in snippet:
                          if line.startswith('    '):
                              continue
                          if line.startswith('```'):
                              in_code_block = not in_code_block
                          if in_code_block:
                              continue
                          m = re.match(hashcounter, line)
                          if m:
                              self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name)
                  self.assertFalse(in_code_block, 'Unclosed code block.')
              else:
                  if f.name != 'add_release_note_snippets_here':
                      self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name)
  
      def test_compiler_options_documented(self):
          '''
          Test that C and C++ compiler options and base options are documented in
          Builtin-Options.md. Only tests the default compiler for the current
          platform on the CI.
          '''
          md = None
          with open('docs/markdown/Builtin-options.md') as f:
              md = f.read()
          self.assertIsNotNone(md)
          env = get_fake_env('', '', '')
          # FIXME: Support other compilers
          cc = env.detect_c_compiler(False)
          cpp = env.detect_cpp_compiler(False)
          for comp in (cc, cpp):
              for opt in comp.get_options().keys():
                  self.assertIn(opt, md)
              for opt in comp.base_options:
                  self.assertIn(opt, md)
          self.assertNotIn('b_unknown', md)
  
      def test_cpu_families_documented(self):
          with open("docs/markdown/Reference-tables.md") as f:
              md = f.read()
          self.assertIsNotNone(md)
  
          sections = list(re.finditer(r"^## (.+)$", md, re.MULTILINE))
          for s1, s2 in zip(sections[::2], sections[1::2]):
              if s1.group(1) == "CPU families":
                  # Extract the content for this section
                  content = md[s1.end():s2.start()]
                  # Find the list entries
                  arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)]
                  # Drop the header
                  arches = set(arches[1:])
                  self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families))
  
      def test_markdown_files_in_sitemap(self):
          '''
          Test that each markdown files in docs/markdown is referenced in sitemap.txt
          '''
          with open("docs/sitemap.txt") as f:
              md = f.read()
          self.assertIsNotNone(md)
          toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE))
          markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md']
          exceptions = ['_Sidebar.md']
          for f in markdownfiles:
              if f not in exceptions:
                  self.assertIn(f, toc)
  
      def test_syntax_highlighting_files(self):
          '''
          Ensure that syntax highlighting files were updated for new functions in
          the global namespace in build files.
          '''
          env = get_fake_env('', '', '')
          interp = Interpreter(FakeBuild(env), mock=True)
          with open('data/syntax-highlighting/vim/syntax/meson.vim') as f:
              res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE)
              defined = set([a.strip() for a in res.group().split('\\')][1:])
              self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys())))
  
  
  class BasePlatformTests(unittest.TestCase):
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          src_root = os.path.join(os.getcwd(), src_root)
          self.src_root = src_root
          self.prefix = '/usr'
          self.libdir = 'lib'
          # Get the backend
          # FIXME: Extract this from argv?
          self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja'))
          self.meson_args = ['--backend=' + self.backend.name]
          self.meson_cross_file = None
          self.meson_command = python_command + [get_meson_script()]
          self.setup_command = self.meson_command + self.meson_args
          self.mconf_command = self.meson_command + ['configure']
          self.mintro_command = self.meson_command + ['introspect']
          self.wrap_command = self.meson_command + ['wrap']
          # Backend-specific build commands
          self.build_command, self.clean_command, self.test_command, self.install_command, \
              self.uninstall_command = get_backend_commands(self.backend)
          # Test directories
          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')
          # Misc stuff
          self.orig_env = os.environ.copy()
          if self.backend is Backend.ninja:
              self.no_rebuild_stdout = 'ninja: no work to do.'
          else:
              # VS doesn't have a stable output when no changes are done
              # XCode backend is untested with unit tests, help welcome!
              self.no_rebuild_stdout = 'UNKNOWN BACKEND {!r}'.format(self.backend.name)
  
          self.builddirs = []
          self.new_builddir()
  
      def change_builddir(self, newdir):
          self.builddir = newdir
          self.privatedir = os.path.join(self.builddir, 'meson-private')
          self.logdir = os.path.join(self.builddir, 'meson-logs')
          self.installdir = os.path.join(self.builddir, 'install')
          self.distdir = os.path.join(self.builddir, 'meson-dist')
          self.mtest_command = self.meson_command + ['test', '-C', self.builddir]
          self.builddirs.append(self.builddir)
  
      def new_builddir(self):
          # In case the directory is inside a symlinked directory, find the real
          # path otherwise we might not find the srcdir from inside the builddir.
          newdir = os.path.realpath(tempfile.mkdtemp())
          self.change_builddir(newdir)
  
      def _print_meson_log(self):
          log = os.path.join(self.logdir, 'meson-log.txt')
          if not os.path.isfile(log):
              print("{!r} doesn't exist".format(log))
              return
          with open(log, 'r', encoding='utf-8') as f:
              print(f.read())
  
      def tearDown(self):
          for path in self.builddirs:
              try:
                  windows_proof_rmtree(path)
              except FileNotFoundError:
                  pass
          os.environ.clear()
          os.environ.update(self.orig_env)
          super().tearDown()
  
      def _run(self, command, workdir=None):
          '''
          Run a command while printing the stdout and stderr to stdout,
          and also return a copy of it
          '''
          # If this call hangs CI will just abort. It is very hard to distinguish
          # between CI issue and test bug in that case. Set timeout and fail loud
          # instead.
          p = subprocess.run(command, stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT, env=os.environ.copy(),
                             universal_newlines=True, cwd=workdir, timeout=60 * 5)
          print(p.stdout)
          if p.returncode != 0:
              if 'MESON_SKIP_TEST' in p.stdout:
                  raise unittest.SkipTest('Project requested skipping.')
              raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout)
          return p.stdout
  
      def init(self, srcdir, extra_args=None, default_args=True, inprocess=False):
          self.assertPathExists(srcdir)
          if extra_args is None:
              extra_args = []
          if not isinstance(extra_args, list):
              extra_args = [extra_args]
          args = [srcdir, self.builddir]
          if default_args:
              args += ['--prefix', self.prefix,
                       '--libdir', self.libdir]
              if self.meson_cross_file:
                  args += ['--cross-file', self.meson_cross_file]
          self.privatedir = os.path.join(self.builddir, 'meson-private')
          if inprocess:
              try:
                  (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args)
                  if 'MESON_SKIP_TEST' in out:
                      raise unittest.SkipTest('Project requested skipping.')
                  if returncode != 0:
                      self._print_meson_log()
                      print('Stdout:\n')
                      print(out)
                      print('Stderr:\n')
                      print(err)
                      raise RuntimeError('Configure failed')
              except:
                  self._print_meson_log()
                  raise
              finally:
                  # Close log file to satisfy Windows file locking
                  mesonbuild.mlog.shutdown()
                  mesonbuild.mlog.log_dir = None
                  mesonbuild.mlog.log_file = None
          else:
              try:
                  out = self._run(self.setup_command + args + extra_args)
              except unittest.SkipTest:
                  raise unittest.SkipTest('Project requested skipping: ' + srcdir)
              except:
                  self._print_meson_log()
                  raise
          return out
  
      def build(self, target=None, extra_args=None):
          if extra_args is None:
              extra_args = []
          # Add arguments for building the target (if specified),
          # and using the build dir (if required, with VS)
          args = get_builddir_target_args(self.backend, self.builddir, target)
          return self._run(self.build_command + args + extra_args, workdir=self.builddir)
  
      def clean(self):
          dir_args = get_builddir_target_args(self.backend, self.builddir, None)
          self._run(self.clean_command + dir_args, workdir=self.builddir)
  
      def run_tests(self, inprocess=False):
          if not inprocess:
              self._run(self.test_command, workdir=self.builddir)
          else:
              run_mtest_inprocess(['-C', self.builddir])
  
      def install(self, *, use_destdir=True):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
          if use_destdir:
              os.environ['DESTDIR'] = self.installdir
          self._run(self.install_command, workdir=self.builddir)
  
      def uninstall(self):
          self._run(self.uninstall_command, workdir=self.builddir)
  
      def run_target(self, target):
          '''
          Run a Ninja target while printing the stdout and stderr to stdout,
          and also return a copy of it
          '''
          return self.build(target=target)
  
      def setconf(self, arg, will_build=True):
          if not isinstance(arg, list):
              arg = [arg]
          if will_build:
              ensure_backend_detects_changes(self.backend)
          self._run(self.mconf_command + arg + [self.builddir])
  
      def wipe(self):
          windows_proof_rmtree(self.builddir)
  
      def utime(self, f):
          ensure_backend_detects_changes(self.backend)
          os.utime(f)
  
      def get_compdb(self):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name))
          with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
              contents = json.load(ifile)
          # If Ninja is using .rsp files, generate them, read their contents, and
          # replace it as the command for all compile commands in the parsed json.
          if len(contents) > 0 and contents[0]['command'].endswith('.rsp'):
              # Pretend to build so that the rsp files are generated
              self.build(extra_args=['-d', 'keeprsp', '-n'])
              for each in contents:
                  # Extract the actual command from the rsp file
                  compiler, rsp = each['command'].split(' @')
                  rsp = os.path.join(self.builddir, rsp)
                  # Replace the command with its contents
                  with open(rsp, 'r', encoding='utf-8') as f:
                      each['command'] = compiler + ' ' + f.read()
          return contents
  
      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, args):
          if isinstance(args, str):
              args = [args]
          out = subprocess.check_output(self.mintro_command + args + [self.builddir],
                                        universal_newlines=True)
          return json.loads(out)
  
      def assertPathEqual(self, path1, path2):
          '''
          Handles a lot of platform-specific quirks related to paths such as
          separator, case-sensitivity, etc.
          '''
          self.assertEqual(PurePath(path1), PurePath(path2))
  
      def assertPathBasenameEqual(self, path, basename):
          msg = '{!r} does not end with {!r}'.format(path, basename)
          # We cannot use os.path.basename because it returns '' when the path
          # ends with '/' for some silly reason. This is not how the UNIX utility
          # `basename` works.
          path_basename = PurePath(path).parts[-1]
          self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
  
      def assertBuildIsNoop(self):
          ret = self.build()
          if self.backend is Backend.ninja:
              self.assertEqual(ret.split('\n')[-2], self.no_rebuild_stdout)
          elif self.backend is Backend.vs:
              # Ensure that some target said that no rebuild was done
              self.assertIn('CustomBuild:\n  All outputs are up-to-date.', ret)
              self.assertIn('ClCompile:\n  All outputs are up-to-date.', ret)
              self.assertIn('Link:\n  All outputs are up-to-date.', ret)
              # Ensure that no targets were built
              clre = re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)
              linkre = re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)
              self.assertNotRegex(ret, clre)
              self.assertNotRegex(ret, linkre)
          elif self.backend is Backend.xcode:
              raise unittest.SkipTest('Please help us fix this test on the xcode backend')
          else:
              raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
  
      def assertRebuiltTarget(self, target):
          ret = self.build()
          if self.backend is Backend.ninja:
              self.assertIn('Linking target {}'.format(target), ret)
          elif self.backend is Backend.vs:
              # Ensure that this target was rebuilt
              linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE)
              self.assertRegex(ret, linkre)
          elif self.backend is Backend.xcode:
              raise unittest.SkipTest('Please help us fix this test on the xcode backend')
          else:
              raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
  
      def assertPathExists(self, path):
          m = 'Path {!r} should exist'.format(path)
          self.assertTrue(os.path.exists(path), msg=m)
  
      def assertPathDoesNotExist(self, path):
          m = 'Path {!r} should not exist'.format(path)
          self.assertFalse(os.path.exists(path), msg=m)
  
  
  class AllPlatformTests(BasePlatformTests):
      '''
      Tests that should run on all platforms
      '''
      def test_default_options_prefix(self):
          '''
          Tests that setting a prefix in default_options in project() works.
          Can't be an ordinary test because we pass --prefix to meson there.
          https://github.com/mesonbuild/meson/issues/1349
          '''
          testdir = os.path.join(self.common_test_dir, '91 default options')
          self.init(testdir, default_args=False)
          opts = self.introspect('--buildoptions')
          for opt in opts:
              if opt['name'] == 'prefix':
                  prefix = opt['value']
          self.assertEqual(prefix, '/absoluteprefix')
  
      def test_absolute_prefix_libdir(self):
          '''
          Tests that setting absolute paths for --prefix and --libdir work. Can't
          be an ordinary test because these are set via the command-line.
          https://github.com/mesonbuild/meson/issues/1341
          https://github.com/mesonbuild/meson/issues/1345
          '''
          testdir = os.path.join(self.common_test_dir, '91 default options')
          prefix = '/someabs'
          libdir = 'libdir'
          extra_args = ['--prefix=' + prefix,
                        # This can just be a relative path, but we want to test
                        # that passing this as an absolute path also works
                        '--libdir=' + prefix + '/' + libdir]
          self.init(testdir, extra_args, default_args=False)
          opts = self.introspect('--buildoptions')
          for opt in opts:
              if opt['name'] == 'prefix':
                  self.assertEqual(prefix, opt['value'])
              elif opt['name'] == 'libdir':
                  self.assertEqual(libdir, opt['value'])
  
      def test_libdir_must_be_inside_prefix(self):
          '''
          Tests that libdir is forced to be inside prefix no matter how it is set.
          Must be a unit test for obvious reasons.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          # libdir being inside prefix is ok
          args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
          self.init(testdir, args)
          self.wipe()
          # libdir not being inside prefix is not ok
          args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
          self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
          self.wipe()
          # libdir must be inside prefix even when set via mesonconf
          self.init(testdir)
          self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False)
  
      def test_prefix_dependent_defaults(self):
          '''
          Tests that configured directory paths are set to prefix dependent
          defaults.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          expected = {
              '/opt': {'prefix': '/opt',
                       'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
                       'infodir': 'share/info',
                       'libexecdir': 'libexec', 'localedir': 'share/locale',
                       'localstatedir': 'var', 'mandir': 'share/man',
                       'sbindir': 'sbin', 'sharedstatedir': 'com',
                       'sysconfdir': 'etc'},
              '/usr': {'prefix': '/usr',
                       'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
                       'infodir': 'share/info',
                       'libexecdir': 'libexec', 'localedir': 'share/locale',
                       'localstatedir': '/var', 'mandir': 'share/man',
                       'sbindir': 'sbin', 'sharedstatedir': '/var/lib',
                       'sysconfdir': '/etc'},
              '/usr/local': {'prefix': '/usr/local',
                             'bindir': 'bin', 'datadir': 'share',
                             'includedir': 'include', 'infodir': 'share/info',
                             'libexecdir': 'libexec',
                             'localedir': 'share/locale',
                             'localstatedir': '/var/local', 'mandir': 'share/man',
                             'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib',
                             'sysconfdir': 'etc'},
              # N.B. We don't check 'libdir' as it's platform dependent, see
              # default_libdir():
          }
          for prefix in expected:
              args = ['--prefix', prefix]
              self.init(testdir, args, default_args=False)
              opts = self.introspect('--buildoptions')
              for opt in opts:
                  name = opt['name']
                  value = opt['value']
                  if name in expected[prefix]:
                      self.assertEqual(value, expected[prefix][name])
              self.wipe()
  
      def test_default_options_prefix_dependent_defaults(self):
          '''
          Tests that setting a prefix in default_options in project() sets prefix
          dependent defaults for other options, and that those defaults can
          be overridden in default_options or by the command line.
          '''
          testdir = os.path.join(self.common_test_dir, '169 default options prefix dependent defaults')
          expected = {
              '':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/sharedstate'},
              '--prefix=/usr':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/sharedstate'},
              '--sharedstatedir=/var/state':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/var/state'},
              '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf':
              {'prefix':         '/usr',
               'sysconfdir':     'sysconf',
               'localstatedir':  '/var',
               'sharedstatedir': '/var/state'},
          }
          for args in expected:
              self.init(testdir, args.split(), default_args=False)
              opts = self.introspect('--buildoptions')
              for opt in opts:
                  name = opt['name']
                  value = opt['value']
                  if name in expected[args]:
                      self.assertEqual(value, expected[args][name])
              self.wipe()
  
      def test_static_library_overwrite(self):
          '''
          Tests that static libraries are never appended to, always overwritten.
          Has to be a unit test because this involves building a project,
          reconfiguring, and building it again so that `ar` is run twice on the
          same static library.
          https://github.com/mesonbuild/meson/issues/1355
          '''
          testdir = os.path.join(self.common_test_dir, '3 static')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          static_linker = env.detect_static_linker(cc)
          if is_windows():
              raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526')
          if not isinstance(static_linker, mesonbuild.linkers.ArLinker):
              raise unittest.SkipTest('static linker is not `ar`')
          # Configure
          self.init(testdir)
          # Get name of static library
          targets = self.introspect('--targets')
          self.assertEqual(len(targets), 1)
          libname = targets[0]['filename']
          # Build and get contents of static library
          self.build()
          before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
          # Filter out non-object-file contents
          before = [f for f in before if f.endswith(('.o', '.obj'))]
          # Static library should contain only one object
          self.assertEqual(len(before), 1, msg=before)
          # Change the source to be built into the static library
          self.setconf('-Dsource=libfile2.c')
          self.build()
          after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
          # Filter out non-object-file contents
          after = [f for f in after if f.endswith(('.o', '.obj'))]
          # Static library should contain only one object
          self.assertEqual(len(after), 1, msg=after)
          # and the object must have changed
          self.assertNotEqual(before, after)
  
      def test_static_compile_order(self):
          '''
          Test that the order of files in a compiler command-line while compiling
          and linking statically is deterministic. This can't be an ordinary test
          case because we need to inspect the compiler database.
          https://github.com/mesonbuild/meson/pull/951
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir)
          compdb = self.get_compdb()
          # Rules will get written out in this order
          self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
          self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
          self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
          self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
          # FIXME: We don't have access to the linker command
  
      def test_run_target_files_path(self):
          '''
          Test that run_targets are run from the correct directory
          https://github.com/mesonbuild/meson/issues/957
          '''
          testdir = os.path.join(self.common_test_dir, '55 run target')
          self.init(testdir)
          self.run_target('check_exists')
  
      def test_install_introspection(self):
          '''
          Tests that the Meson introspection API exposes install filenames correctly
          https://github.com/mesonbuild/meson/issues/829
          '''
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          intro = self.introspect('--targets')
          if intro[0]['type'] == 'executable':
              intro = intro[::-1]
          self.assertPathEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
          self.assertPathEqual(intro[1]['install_filename'], '/usr/bin/prog' + exe_suffix)
  
      def test_uninstall(self):
          exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          self.assertPathDoesNotExist(exename)
          self.install()
          self.assertPathExists(exename)
          self.uninstall()
          self.assertPathDoesNotExist(exename)
  
      def test_forcefallback(self):
          testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
          self.init(testdir, ['--wrap-mode=forcefallback'])
          self.build()
          self.run_tests()
  
      def test_testsetups(self):
          if not shutil.which('valgrind'):
                  raise unittest.SkipTest('Valgrind not installed.')
          testdir = os.path.join(self.unit_test_dir, '2 testsetups')
          self.init(testdir)
          self.build()
          # Run tests without setup
          self.run_tests()
          with open(os.path.join(self.logdir, 'testlog.txt')) as f:
              basic_log = f.read()
          # Run buggy test with setup that has env that will make it fail
          self.assertRaises(subprocess.CalledProcessError,
                            self._run, self.mtest_command + ['--setup=valgrind'])
          with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
              vg_log = f.read()
          self.assertFalse('TEST_ENV is set' in basic_log)
          self.assertFalse('Memcheck' in basic_log)
          self.assertTrue('TEST_ENV is set' in vg_log)
          self.assertTrue('Memcheck' in vg_log)
          # Run buggy test with setup without env that will pass
          self._run(self.mtest_command + ['--setup=wrapper'])
          # Setup with no properties works
          self._run(self.mtest_command + ['--setup=empty'])
          # Setup with only env works
          self._run(self.mtest_command + ['--setup=onlyenv'])
          self._run(self.mtest_command + ['--setup=onlyenv2'])
          self._run(self.mtest_command + ['--setup=onlyenv3'])
          # Setup with only a timeout works
          self._run(self.mtest_command + ['--setup=timeout'])
  
      def test_testsetup_selection(self):
          testdir = os.path.join(self.unit_test_dir, '14 testsetup selection')
          self.init(testdir)
          self.build()
  
          # Run tests without setup
          self.run_tests()
  
          self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo'])
          self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:'])
  
          self._run(self.mtest_command + ['--setup=worksforall'])
          self._run(self.mtest_command + ['--setup=main:worksforall'])
  
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:'])
          self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:'])
          self._run(self.mtest_command + ['--setup=bar:onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=foo:onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=main:onlyinbar'])
  
      def assertFailedTestCount(self, failure_count, command):
          try:
              self._run(command)
              self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
          except subprocess.CalledProcessError as e:
              self.assertEqual(e.returncode, failure_count)
  
      def test_suite_selection(self):
          testdir = os.path.join(self.unit_test_dir, '4 suite selection')
          self.init(testdir)
          self.build()
  
          self.assertFailedTestCount(3, self.mtest_command)
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
          self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
  
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
  
      def test_build_by_default(self):
          testdir = os.path.join(self.common_test_dir, '134 build by default')
          self.init(testdir)
          self.build()
          genfile1 = os.path.join(self.builddir, 'generated1.dat')
          genfile2 = os.path.join(self.builddir, 'generated2.dat')
          exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix)
          exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix)
          self.assertPathExists(genfile1)
          self.assertPathExists(genfile2)
          self.assertPathDoesNotExist(exe1)
          self.assertPathDoesNotExist(exe2)
          self.build(target=('fooprog' + exe_suffix))
          self.assertPathExists(exe1)
          self.build(target=('barprog' + exe_suffix))
          self.assertPathExists(exe2)
  
      def test_internal_include_order(self):
          testdir = os.path.join(self.common_test_dir, '135 include order')
          self.init(testdir)
          execmd = fxecmd = None
          for cmd in self.get_compdb():
              if 'someexe' in cmd['command']:
                  execmd = cmd['command']
                  continue
              if 'somefxe' in cmd['command']:
                  fxecmd = cmd['command']
                  continue
          if not execmd or not fxecmd:
              raise Exception('Could not find someexe and somfxe commands')
          # Check include order for 'someexe'
          incs = [a for a in shlex.split(execmd) if a.startswith("-I")]
          self.assertEqual(len(incs), 9)
          # target private dir
          self.assertPathEqual(incs[0], "-Isub4/sub4@@someexe@exe")
          # target build subdir
          self.assertPathEqual(incs[1], "-Isub4")
          # target source subdir
          self.assertPathBasenameEqual(incs[2], 'sub4')
          # include paths added via per-target c_args: ['-I'...]
          self.assertPathBasenameEqual(incs[3], 'sub3')
          # target include_directories: build dir
          self.assertPathEqual(incs[4], "-Isub2")
          # target include_directories: source dir
          self.assertPathBasenameEqual(incs[5], 'sub2')
          # target internal dependency include_directories: build dir
          self.assertPathEqual(incs[6], "-Isub1")
          # target internal dependency include_directories: source dir
          self.assertPathBasenameEqual(incs[7], 'sub1')
          # custom target include dir
          self.assertPathEqual(incs[8], '-Ictsub')
          # Check include order for 'somefxe'
          incs = [a for a in shlex.split(fxecmd) if a.startswith('-I')]
          self.assertEqual(len(incs), 9)
          # target private dir
          self.assertPathEqual(incs[0], '-Isomefxe@exe')
          # target build dir
          self.assertPathEqual(incs[1], '-I.')
          # target source dir
          self.assertPathBasenameEqual(incs[2], os.path.basename(testdir))
          # target internal dependency correct include_directories: build dir
          self.assertPathEqual(incs[3], "-Isub4")
          # target internal dependency correct include_directories: source dir
          self.assertPathBasenameEqual(incs[4], 'sub4')
          # target internal dependency dep include_directories: build dir
          self.assertPathEqual(incs[5], "-Isub1")
          # target internal dependency dep include_directories: source dir
          self.assertPathBasenameEqual(incs[6], 'sub1')
          # target internal dependency wrong include_directories: build dir
          self.assertPathEqual(incs[7], "-Isub2")
          # target internal dependency wrong include_directories: source dir
          self.assertPathBasenameEqual(incs[8], 'sub2')
  
      def test_compiler_detection(self):
          '''
          Test that automatic compiler detection and setting from the environment
          both work just fine. This is needed because while running project tests
          and other unit tests, we always read CC/CXX/etc from the environment.
          '''
          gnu = mesonbuild.compilers.GnuCompiler
          clang = mesonbuild.compilers.ClangCompiler
          intel = mesonbuild.compilers.IntelCompiler
          msvc = mesonbuild.compilers.VisualStudioCCompiler
          ar = mesonbuild.linkers.ArLinker
          lib = mesonbuild.linkers.VisualStudioLinker
          langs = [('c', 'CC'), ('cpp', 'CXX')]
          if not is_windows():
              langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
          testdir = os.path.join(self.unit_test_dir, '5 compiler detection')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          for lang, evar in langs:
              # Detect with evar and do sanity checks on that
              if evar in os.environ:
                  ecc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
                  self.assertTrue(ecc.version)
                  elinker = env.detect_static_linker(ecc)
                  # Pop it so we don't use it for the next detection
                  evalue = os.environ.pop(evar)
                  # Very rough/strict heuristics. Would never work for actual
                  # compiler detection, but should be ok for the tests.
                  ebase = os.path.basename(evalue)
                  if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')):
                      self.assertIsInstance(ecc, gnu)
                      self.assertIsInstance(elinker, ar)
                  elif 'clang' in ebase:
                      self.assertIsInstance(ecc, clang)
                      self.assertIsInstance(elinker, ar)
                  elif ebase.startswith('ic'):
                      self.assertIsInstance(ecc, intel)
                      self.assertIsInstance(elinker, ar)
                  elif ebase.startswith('cl'):
                      self.assertIsInstance(ecc, msvc)
                      self.assertIsInstance(elinker, lib)
                  else:
                      raise AssertionError('Unknown compiler {!r}'.format(evalue))
                  # Check that we actually used the evalue correctly as the compiler
                  self.assertEqual(ecc.get_exelist(), shlex.split(evalue))
              # Do auto-detection of compiler based on platform, PATH, etc.
              cc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
              self.assertTrue(cc.version)
              linker = env.detect_static_linker(cc)
              # Check compiler type
              if isinstance(cc, gnu):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.GCC_OSX)
                  elif is_windows():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.GCC_MINGW)
                  elif is_cygwin():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.GCC_CYGWIN)
                  else:
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.GCC_STANDARD)
              if isinstance(cc, clang):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.CLANG_OSX)
                  elif is_windows():
                      # Not implemented yet
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.CLANG_MINGW)
                  else:
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.CLANG_STANDARD)
              if isinstance(cc, intel):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.ICC_OSX)
                  elif is_windows():
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.ICC_WIN)
                  else:
                      self.assertEqual(cc.compiler_type, mesonbuild.compilers.CompilerType.ICC_STANDARD)
              if isinstance(cc, msvc):
                  self.assertTrue(is_windows())
                  self.assertIsInstance(linker, lib)
                  self.assertEqual(cc.id, 'msvc')
                  self.assertTrue(hasattr(cc, 'is_64'))
                  # If we're in the appveyor CI, we know what the compiler will be
                  if 'arch' in os.environ:
                      if os.environ['arch'] == 'x64':
                          self.assertTrue(cc.is_64)
                      else:
                          self.assertFalse(cc.is_64)
              # Set evar ourselves to a wrapper script that just calls the same
              # exelist + some argument. This is meant to test that setting
              # something like `ccache gcc -pipe` or `distcc ccache gcc` works.
              wrapper = os.path.join(testdir, 'compiler wrapper.py')
              wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG']
              wrappercc_s = ''
              for w in wrappercc:
                  wrappercc_s += shlex.quote(w) + ' '
              os.environ[evar] = wrappercc_s
              wcc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
              # Check static linker too
              wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args()
              wrapperlinker_s = ''
              for w in wrapperlinker:
                  wrapperlinker_s += shlex.quote(w) + ' '
              os.environ['AR'] = wrapperlinker_s
              wlinker = env.detect_static_linker(wcc)
              # Must be the same type since it's a wrapper around the same exelist
              self.assertIs(type(cc), type(wcc))
              self.assertIs(type(linker), type(wlinker))
              # Ensure that the exelist is correct
              self.assertEqual(wcc.get_exelist(), wrappercc)
              self.assertEqual(wlinker.get_exelist(), wrapperlinker)
              # Ensure that the version detection worked correctly
              self.assertEqual(cc.version, wcc.version)
              if hasattr(cc, 'is_64'):
                  self.assertEqual(cc.is_64, wcc.is_64)
  
      def test_always_prefer_c_compiler_for_asm(self):
          testdir = os.path.join(self.common_test_dir, '138 c cpp and asm')
          # Skip if building with MSVC
          env = get_fake_env(testdir, self.builddir, self.prefix)
          if env.detect_c_compiler(False).get_id() == 'msvc':
              raise unittest.SkipTest('MSVC can\'t compile assembly')
          self.init(testdir)
          commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}}
          for cmd in self.get_compdb():
              # Get compiler
              split = shlex.split(cmd['command'])
              if split[0] == 'ccache':
                  compiler = split[1]
              else:
                  compiler = split[0]
              # Classify commands
              if 'Ic-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['c-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['c-asm']['c'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
              elif 'Icpp-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['cpp-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['cpp-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
              elif 'Ic-cpp-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['c-cpp-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['c-cpp-asm']['c'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['c-cpp-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command']))
              elif 'Icpp-c-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['cpp-c-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['cpp-c-asm']['c'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['cpp-c-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command']))
              else:
                  raise AssertionError('Unknown command {!r} found'.format(cmd['command']))
          # Check that .S files are always built with the C compiler
          self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c'])
          self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm'])
          self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c'])
          self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c'])
          self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c'])
          self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp'])
          self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp'])
          self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp'])
          # Check that the c-asm target is always linked with the C linker
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('build c-asm.*: c_LINKER', contents)
          self.assertIsNotNone(m, msg=contents)
  
      def test_preprocessor_checks_CPPFLAGS(self):
          '''
          Test that preprocessor compiler checks read CPPFLAGS but not CFLAGS
          '''
          testdir = os.path.join(self.common_test_dir, '137 get define')
          define = 'MESON_TEST_DEFINE_VALUE'
          # NOTE: this list can't have \n, ' or "
          # \n is never substituted by the GNU pre-processor via a -D define
          # ' and " confuse shlex.split() even when they are escaped
          # % and # confuse the MSVC preprocessor
          # !, ^, *, and < confuse lcc preprocessor
          value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`'
          os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value)
          os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define)
          self.init(testdir, ['-D{}={}'.format(define, value)])
  
      def test_custom_target_exe_data_deterministic(self):
          testdir = os.path.join(self.common_test_dir, '114 custom target capture')
          self.init(testdir)
          meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.wipe()
          self.init(testdir)
          meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
  
      def test_source_changes_cause_rebuild(self):
          '''
          Test that changes to sources and headers cause rebuilds, but not
          changes to unused files (as determined by the dependency file) in the
          input files list.
          '''
          testdir = os.path.join(self.common_test_dir, '20 header in file list')
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Changing mtime of header.h should rebuild everything
          self.utime(os.path.join(testdir, 'header.h'))
          self.assertRebuiltTarget('prog')
  
      def test_custom_target_changes_cause_rebuild(self):
          '''
          Test that in a custom target, changes to the input files, the
          ExternalProgram, and any File objects on the command-line cause
          a rebuild.
          '''
          testdir = os.path.join(self.common_test_dir, '61 custom header generator')
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Changing mtime of these should rebuild everything
          for f in ('input.def', 'makeheader.py', 'somefile.txt'):
              self.utime(os.path.join(testdir, f))
              self.assertRebuiltTarget('prog')
  
      def test_static_library_lto(self):
          '''
          Test that static libraries can be built with LTO and linked to
          executables. On Linux, this requires the use of gcc-ar.
          https://github.com/mesonbuild/meson/issues/1646
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir, extra_args='-Db_lto=true')
          self.build()
          self.run_tests()
  
      def test_dist_git(self):
          if not shutil.which('git'):
              raise unittest.SkipTest('Git not found')
  
          try:
              self.dist_impl(_git_init)
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the git files so cleaning up the dir
              # fails sometimes.
              pass
  
      def test_dist_hg(self):
          if not shutil.which('hg'):
              raise unittest.SkipTest('Mercurial not found')
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Dist is only supported with Ninja')
  
          def hg_init(project_dir):
              subprocess.check_call(['hg', 'init'], cwd=project_dir)
              with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w') as f:
                  print('[ui]', file=f)
                  print('username=Author Person <teh_coderz@example.com>', file=f)
              subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir)
              subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir)
  
          try:
              self.dist_impl(hg_init)
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the hg files so cleaning up the dir
              # fails sometimes.
              pass
  
      def test_dist_git_script(self):
          if not shutil.which('git'):
              raise unittest.SkipTest('Git not found')
  
          try:
              with tempfile.TemporaryDirectory() as tmpdir:
                  project_dir = os.path.join(tmpdir, 'a')
                  shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'),
                                  project_dir)
                  _git_init(project_dir)
                  self.init(project_dir)
                  self.build('dist')
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the git files so cleaning up the dir
              # fails sometimes.
              pass
  
      def dist_impl(self, vcs_init):
          # Create this on the fly because having rogue .git directories inside
          # the source tree leads to all kinds of trouble.
          with tempfile.TemporaryDirectory() as project_dir:
              with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile:
                  ofile.write('''project('disttest', 'c', version : '1.4.3')
  e = executable('distexe', 'distexe.c')
  test('dist test', e)
  ''')
              with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile:
                  ofile.write('''#include<stdio.h>
  
  int main(int argc, char **argv) {
      printf("I am a distribution test.\\n");
      return 0;
  }
  ''')
              vcs_init(project_dir)
              self.init(project_dir)
              self.build('dist')
              distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz')
              checksumfile = distfile + '.sha256sum'
              self.assertPathExists(distfile)
              self.assertPathExists(checksumfile)
  
      def test_rpath_uses_ORIGIN(self):
          '''
          Test that built targets use $ORIGIN in rpath, which ensures that they
          are relocatable and ensures that builds are reproducible since the
          build directory won't get embedded into the built binaries.
          '''
          if is_windows() or is_cygwin():
              raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
          testdir = os.path.join(self.common_test_dir, '43 library chain')
          self.init(testdir)
          self.build()
          for each in ('prog', 'subdir/liblib1.so', ):
              rpath = get_rpath(os.path.join(self.builddir, each))
              self.assertTrue(rpath)
              if is_dragonflybsd():
                  # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath,
                  # so ignore that.
                  self.assertTrue(rpath.startswith('/usr/lib/gcc'))
                  rpaths = rpath.split(':')[1:]
              else:
                  rpaths = rpath.split(':')
              for path in rpaths:
                  self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path))
          # These two don't link to anything else, so they do not need an rpath entry.
          for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'):
              rpath = get_rpath(os.path.join(self.builddir, each))
              if is_dragonflybsd():
                  # The rpath should be equal to /usr/lib/gccVERSION
                  self.assertTrue(rpath.startswith('/usr/lib/gcc'))
                  self.assertEqual(len(rpath.split(':')), 1)
              else:
                  self.assertTrue(rpath is None)
  
      def test_dash_d_dedup(self):
          testdir = os.path.join(self.unit_test_dir, '9 d dedup')
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          self.assertTrue('-D FOO -D BAR' in cmd or
                          '"-D" "FOO" "-D" "BAR"' in cmd or
                          '/D FOO /D BAR' in cmd or
                          '"/D" "FOO" "/D" "BAR"' in cmd)
  
      def test_all_forbidden_targets_tested(self):
          '''
          Test that all forbidden targets are tested in the '155 reserved targets'
          test. Needs to be a unit test because it accesses Meson internals.
          '''
          testdir = os.path.join(self.common_test_dir, '155 reserved targets')
          targets = mesonbuild.coredata.forbidden_target_names
          # We don't actually define a target with this name
          targets.pop('build.ninja')
          # Remove this to avoid multiple entries with the same name
          # but different case.
          targets.pop('PHONY')
          for i in targets:
              self.assertPathExists(os.path.join(testdir, i))
  
      def detect_prebuild_env(self):
          env = get_fake_env('', self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          stlinker = env.detect_static_linker(cc)
          if mesonbuild.mesonlib.is_windows():
              object_suffix = 'obj'
              shared_suffix = 'dll'
          elif mesonbuild.mesonlib.is_cygwin():
              object_suffix = 'o'
              shared_suffix = 'dll'
          elif mesonbuild.mesonlib.is_osx():
              object_suffix = 'o'
              shared_suffix = 'dylib'
          else:
              object_suffix = 'o'
              shared_suffix = 'so'
          return (cc, stlinker, object_suffix, shared_suffix)
  
      def pbcompile(self, compiler, source, objectfile, extra_args=[]):
          cmd = compiler.get_exelist()
          if compiler.id == 'msvc':
              cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args
          else:
              cmd += ['-c', source, '-o', objectfile] + extra_args
          subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  
      def test_prebuilt_object(self):
          (compiler, _, object_suffix, _) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '15 prebuilt object')
          source = os.path.join(tdir, 'source.c')
          objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix)
          self.pbcompile(compiler, source, objectfile)
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(objectfile)
  
      def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None):
          if extra_args is None:
              extra_args = []
          if compiler.id == 'msvc':
              link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile]
          else:
              link_cmd = ['ar', 'csr', outfile, objectfile]
          link_cmd = linker.get_exelist()
          link_cmd += linker.get_always_args()
          link_cmd += linker.get_std_link_args()
          link_cmd += linker.get_output_args(outfile)
          link_cmd += [objectfile]
          self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
          try:
              subprocess.check_call(link_cmd)
          finally:
              os.unlink(objectfile)
  
      def test_prebuilt_static_lib(self):
          (cc, stlinker, object_suffix, _) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '16 prebuilt static')
          source = os.path.join(tdir, 'libdir/best.c')
          objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix)
          stlibfile = os.path.join(tdir, 'libdir/libbest.a')
          self.build_static_lib(cc, stlinker, source, objectfile, stlibfile)
          # Run the test
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(stlibfile)
  
      def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None):
          if extra_args is None:
              extra_args = []
          if compiler.id == 'msvc':
              link_cmd = ['link', '/NOLOGO', '/DLL', '/DEBUG',
                          '/IMPLIB:' + impfile, '/OUT:' + outfile, objectfile]
          else:
              extra_args += ['-fPIC']
              link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile]
              if not mesonbuild.mesonlib.is_osx():
                  link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)]
          self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
          try:
              subprocess.check_call(link_cmd)
          finally:
              os.unlink(objectfile)
  
      def test_prebuilt_shared_lib(self):
          (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
          source = os.path.join(tdir, 'alexandria.c')
          objectfile = os.path.join(tdir, 'alexandria.' + object_suffix)
          impfile = os.path.join(tdir, 'alexandria.lib')
          if cc.id == 'msvc':
              shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix)
          elif is_cygwin():
              shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix)
          else:
              shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix)
          self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
          # Run the test
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(shlibfile)
              if mesonbuild.mesonlib.is_windows():
                  # Clean up all the garbage MSVC writes in the
                  # source tree.
                  for fname in glob(os.path.join(tdir, 'alexandria.*')):
                      if os.path.splitext(fname)[1] not in ['.c', '.h']:
                          os.unlink(fname)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_static(self):
          '''
          Test that the we prefer static libraries when `static: true` is
          passed to dependency() with pkg-config. Can't be an ordinary test
          because we need to build libs and try to find them from meson.build
  
          Also test that it's not a hard error to have unsatisfiable library deps
          since system libraries -lm will never be found statically.
          https://github.com/mesonbuild/meson/issues/2785
          '''
          (cc, stlinker, objext, shext) = self.detect_prebuild_env()
          testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static')
          source = os.path.join(testdir, 'foo.c')
          objectfile = os.path.join(testdir, 'foo.' + objext)
          stlibfile = os.path.join(testdir, 'libfoo.a')
          impfile = os.path.join(testdir, 'foo.lib')
          if cc.id == 'msvc':
              shlibfile = os.path.join(testdir, 'foo.' + shext)
          elif is_cygwin():
              shlibfile = os.path.join(testdir, 'cygfoo.' + shext)
          else:
              shlibfile = os.path.join(testdir, 'libfoo.' + shext)
          # Build libs
          self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC'])
          self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
          # Run test
          os.environ['PKG_CONFIG_LIBDIR'] = self.builddir
          try:
              self.init(testdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(stlibfile)
              os.unlink(shlibfile)
              if mesonbuild.mesonlib.is_windows():
                  # Clean up all the garbage MSVC writes in the
                  # source tree.
                  for fname in glob(os.path.join(testdir, 'foo.*')):
                      if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']:
                          os.unlink(fname)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_gen_escaping(self):
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen')
          prefix = '/usr/with spaces'
          libdir = 'lib'
          self.init(testdir, extra_args=['--prefix=' + prefix,
                                         '--libdir=' + libdir])
          # Find foo dependency
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          env = get_fake_env(testdir, self.builddir, self.prefix)
          kwargs = {'required': True, 'silent': True}
          foo_dep = PkgConfigDependency('libfoo', env, kwargs)
          # Ensure link_args are properly quoted
          libdir = PurePath(prefix) / PurePath(libdir)
          link_args = ['-L' + libdir.as_posix(), '-lfoo']
          self.assertEqual(foo_dep.get_link_args(), link_args)
          # Ensure include args are properly quoted
          incdir = PurePath(prefix) / PurePath('include')
          cargs = ['-I' + incdir.as_posix()]
          self.assertEqual(foo_dep.get_compile_args(), cargs)
  
      def test_array_option_change(self):
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': ['foo', 'bar'],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir)
          original = get_opt()
          self.assertDictEqual(original, expected)
  
          expected['value'] = ['oink', 'boink']
          self.setconf('-Dlist=oink,boink')
          changed = get_opt()
          self.assertEqual(changed, expected)
  
      def test_array_option_bad_change(self):
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': ['foo', 'bar'],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir)
          original = get_opt()
          self.assertDictEqual(original, expected)
          with self.assertRaises(subprocess.CalledProcessError):
              self.setconf('-Dlist=bad')
          changed = get_opt()
          self.assertDictEqual(changed, expected)
  
      def test_array_option_empty_equivalents(self):
          """Array options treat -Dopt=[] and -Dopt= as equivalent."""
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': [],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir, extra_args='-Dlist=')
          original = get_opt()
          self.assertDictEqual(original, expected)
  
      def opt_has(self, name, value):
          res = self.introspect('--buildoptions')
          found = False
          for i in res:
              if i['name'] == name:
                  self.assertEqual(i['value'], value)
                  found = True
                  break
          self.assertTrue(found, "Array option not found in introspect data.")
  
      def test_free_stringarray_setting(self):
          testdir = os.path.join(self.common_test_dir, '44 options')
          self.init(testdir)
          self.opt_has('free_array_opt', [])
          self.setconf('-Dfree_array_opt=foo,bar', will_build=False)
          self.opt_has('free_array_opt', ['foo', 'bar'])
          self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False)
          self.opt_has('free_array_opt', ['a,b', 'c,d'])
  
      def test_subproject_promotion(self):
          testdir = os.path.join(self.unit_test_dir, '12 promote')
          workdir = os.path.join(self.builddir, 'work')
          shutil.copytree(testdir, workdir)
          spdir = os.path.join(workdir, 'subprojects')
          s3dir = os.path.join(spdir, 's3')
          scommondir = os.path.join(spdir, 'scommon')
          self.assertFalse(os.path.isdir(s3dir))
          subprocess.check_call(self.wrap_command + ['promote', 's3'], cwd=workdir)
          self.assertTrue(os.path.isdir(s3dir))
          self.assertFalse(os.path.isdir(scommondir))
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'],
                                              cwd=workdir,
                                              stdout=subprocess.DEVNULL), 0)
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'],
                                              cwd=workdir,
                                              stderr=subprocess.DEVNULL), 0)
          self.assertFalse(os.path.isdir(scommondir))
          subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir)
          self.assertTrue(os.path.isdir(scommondir))
          promoted_wrap = os.path.join(spdir, 'athing.wrap')
          self.assertFalse(os.path.isfile(promoted_wrap))
          subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir)
          self.assertTrue(os.path.isfile(promoted_wrap))
          self.init(workdir)
          self.build()
  
      def test_subproject_promotion_wrap(self):
          testdir = os.path.join(self.unit_test_dir, '44 promote wrap')
          workdir = os.path.join(self.builddir, 'work')
          shutil.copytree(testdir, workdir)
          spdir = os.path.join(workdir, 'subprojects')
  
          ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap')
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'],
                                              cwd=workdir,
                                              stdout=subprocess.DEVNULL), 0)
          self.assertFalse(os.path.isfile(ambiguous_wrap))
          subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir)
          self.assertTrue(os.path.isfile(ambiguous_wrap))
  
      def test_warning_location(self):
          tdir = os.path.join(self.unit_test_dir, '22 warning location')
          out = self.init(tdir)
          for expected in [
              r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.',
              r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.',
              r'meson.build:6: WARNING: a warning of some sort',
              r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning',
              r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.',
              r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.",
              r'meson.build:1: WARNING: Passed invalid keyword argument "invalid".',
          ]:
              self.assertRegex(out, re.escape(expected))
  
      def test_permitted_method_kwargs(self):
          tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs')
          out = self.init(tdir)
          for expected in [
              r'WARNING: Passed invalid keyword argument "prefixxx".',
              r'WARNING: Passed invalid keyword argument "argsxx".',
              r'WARNING: Passed invalid keyword argument "invalidxx".',
          ]:
              self.assertRegex(out, re.escape(expected))
  
      def test_templates(self):
          ninja = detect_ninja()
          if ninja is None:
              raise unittest.SkipTest('This test currently requires ninja. Fix this once "meson build" works.')
          for lang in ('c', 'cpp'):
              for type in ('executable', 'library'):
                  with tempfile.TemporaryDirectory() as tmpdir:
                      self._run(self.meson_command + ['init', '--language', lang, '--type', type],
                                workdir=tmpdir)
                      self._run(self.setup_command + ['--backend=ninja', 'builddir'],
                                workdir=tmpdir)
                      self._run(ninja,
                                workdir=os.path.join(tmpdir, 'builddir'))
              with tempfile.TemporaryDirectory() as tmpdir:
                  with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f:
                      f.write('int main() {}')
                  self._run(self.meson_command + ['init', '-b'], workdir=tmpdir)
  
      # The test uses mocking and thus requires that
      # the current process is the one to run the Meson steps.
      # If we are using an external test executable (most commonly
      # in Debian autopkgtests) then the mocking won't work.
      @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.')
      def test_cross_file_system_paths(self):
          if is_windows():
              raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)')
  
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          cross_content = textwrap.dedent("""\
              [binaries]
              c = '/usr/bin/cc'
              ar = '/usr/bin/ar'
              strip = '/usr/bin/ar'
  
              [properties]
  
              [host_machine]
              system = 'linux'
              cpu_family = 'x86'
              cpu = 'i686'
              endian = 'little'
              """)
  
          with tempfile.TemporaryDirectory() as d:
              dir_ = os.path.join(d, 'meson', 'cross')
              os.makedirs(dir_)
              with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
                  f.write(cross_content)
              name = os.path.basename(f.name)
  
              with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}):
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
              with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}):
                  os.environ.pop('XDG_DATA_HOME', None)
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
          with tempfile.TemporaryDirectory() as d:
              dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross')
              os.makedirs(dir_)
              with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
                  f.write(cross_content)
              name = os.path.basename(f.name)
  
              with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)):
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
      def test_compiler_run_command(self):
          '''
          The test checks that the compiler object can be passed to
          run_command().
          '''
          testdir = os.path.join(self.unit_test_dir, '24 compiler run_command')
          self.init(testdir)
  
      def test_identical_target_name_in_subproject_flat_layout(self):
          '''
          Test that identical targets in different subprojects do not collide
          if layout is flat.
          '''
          testdir = os.path.join(self.common_test_dir, '178 identical target name in subproject flat layout')
          self.init(testdir, extra_args=['--layout=flat'])
          self.build()
  
      def test_identical_target_name_in_subdir_flat_layout(self):
          '''
          Test that identical targets in different subdirs do not collide
          if layout is flat.
          '''
          testdir = os.path.join(self.common_test_dir, '187 same target name flat layout')
          self.init(testdir, extra_args=['--layout=flat'])
          self.build()
  
      def test_flock(self):
          exception_raised = False
          with tempfile.TemporaryDirectory() as tdir:
              os.mkdir(os.path.join(tdir, 'meson-private'))
              with BuildDirLock(tdir):
                  try:
                      with BuildDirLock(tdir):
                          pass
                  except MesonException:
                      exception_raised = True
          self.assertTrue(exception_raised, 'Double locking did not raise exception.')
  
      @unittest.skipIf(is_osx(), 'Test not applicable to OSX')
      def test_check_module_linking(self):
          """
          Test that link_with: a shared module issues a warning
          https://github.com/mesonbuild/meson/issues/2865
          (That an error is raised on OSX is exercised by test failing/78)
          """
          tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking')
          out = self.init(tdir)
          msg = ('''WARNING: target links against shared modules. This is not
  recommended as it is not supported on some platforms''')
          self.assertIn(msg, out)
  
      def test_ndebug_if_release_disabled(self):
          testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
          self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release'])
          self.build()
          exe = os.path.join(self.builddir, 'main')
          self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip())
  
      def test_ndebug_if_release_enabled(self):
          testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
          self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release'])
          self.build()
          exe = os.path.join(self.builddir, 'main')
          self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip())
  
      def test_guessed_linker_dependencies(self):
          '''
          Test that meson adds dependencies for libraries based on the final
          linker command line.
          '''
          # build library
          testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies')
          testdirlib = os.path.join(testdirbase, 'lib')
          extra_args = None
          env = get_fake_env(testdirlib, self.builddir, self.prefix)
          if env.detect_c_compiler(False).get_id() != 'msvc':
              # static libraries are not linkable with -l with msvc because meson installs them
              # as .a files which unix_args_to_native will not know as it expects libraries to use
              # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc
              # this tests needs to use shared libraries to test the path resolving logic in the
              # dependency generation code path.
              extra_args = ['--default-library', 'static']
          self.init(testdirlib, extra_args=extra_args)
          self.build()
          self.install()
          libbuilddir = self.builddir
          installdir = self.installdir
          libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib')
  
          # build user of library
          self.new_builddir()
          # replace is needed because meson mangles platform pathes passed via LDFLAGS
          os.environ["LDFLAGS"] = '-L{}'.format(libdir.replace('\\', '/'))
          self.init(os.path.join(testdirbase, 'exe'))
          del os.environ["LDFLAGS"]
          self.build()
          self.assertBuildIsNoop()
  
          # rebuild library
          exebuilddir = self.builddir
          self.installdir = installdir
          self.builddir = libbuilddir
          # Microsoft's compiler is quite smart about touching import libs on changes,
          # so ensure that there is actually a change in symbols.
          self.setconf('-Dmore_exports=true')
          self.build()
          self.install()
          # no ensure_backend_detects_changes needed because self.setconf did that already
  
          # assert user of library will be rebuild
          self.builddir = exebuilddir
          self.assertRebuiltTarget('app')
  
      def test_conflicting_d_dash_option(self):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          with self.assertRaises(subprocess.CalledProcessError) as e:
              self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar'])
              # Just to ensure that we caught the correct error
              self.assertIn('passed as both', e.stderr)
  
      def _test_same_option_twice(self, arg, args):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          self.init(testdir, extra_args=args)
          opts = self.introspect('--buildoptions')
          for item in opts:
              if item['name'] == arg:
                  self.assertEqual(item['value'], 'bar')
                  return
          raise Exception('Missing {} value?'.format(arg))
  
      def test_same_dash_option_twice(self):
          self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar'])
  
      def test_same_d_option_twice(self):
          self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar'])
  
      def test_same_project_d_option_twice(self):
          self._test_same_option_twice('one', ['-Done=foo', '-Done=bar'])
  
      def _test_same_option_twice_configure(self, arg, args):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          self.init(testdir)
          self.setconf(args)
          opts = self.introspect('--buildoptions')
          for item in opts:
              if item['name'] == arg:
                  self.assertEqual(item['value'], 'bar')
                  return
          raise Exception('Missing {} value?'.format(arg))
  
      def test_same_dash_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'bindir', ['--bindir=foo', '--bindir=bar'])
  
      def test_same_d_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'bindir', ['-Dbindir=foo', '-Dbindir=bar'])
  
      def test_same_project_d_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'one', ['-Done=foo', '-Done=bar'])
  
      def test_command_line(self):
          testdir = os.path.join(self.unit_test_dir, '34 command line')
  
          # Verify default values when passing no args
          self.init(testdir)
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'static')
          self.assertEqual(obj.builtins['warning_level'].value, '1')
          self.assertEqual(obj.user_options['set_sub_opt'].value, True)
          self.assertEqual(obj.user_options['subp:subp_opt'].value, 'default3')
          self.wipe()
  
          # warning_level is special, it's --warnlevel instead of --warning-level
          # for historical reasons
          self.init(testdir, extra_args=['--warnlevel=2'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '2')
          self.setconf('--warnlevel=3')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '3')
          self.wipe()
  
          # But when using -D syntax, it should be 'warning_level'
          self.init(testdir, extra_args=['-Dwarning_level=2'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '2')
          self.setconf('-Dwarning_level=3')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '3')
          self.wipe()
  
          # Mixing --option and -Doption is forbidden
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('as both', cm.exception.output)
          self.init(testdir)
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.setconf(['--warnlevel=1', '-Dwarning_level=3'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('as both', cm.exception.output)
          self.wipe()
  
          # --default-library should override default value from project()
          self.init(testdir, extra_args=['--default-library=both'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'both')
          self.setconf('--default-library=shared')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'shared')
          if self.backend is Backend.ninja:
              # reconfigure target works only with ninja backend
              self.build('reconfigure')
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['default_library'].value, 'shared')
          self.wipe()
  
          # Should warn on unknown options
          out = self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo'])
          self.assertIn('Unknown options: "bad, foo, wrong_link_args"', out)
          self.wipe()
  
          # Should fail on malformed option
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(testdir, extra_args=['-Dfoo'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
          self.init(testdir)
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.setconf('-Dfoo')
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
          self.wipe()
  
          # It is not an error to set wrong option for unknown subprojects or
          # language because we don't have control on which one will be selected.
          self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1'])
          self.wipe()
  
          # Test we can set subproject option
          self.init(testdir, extra_args=['-Dsubp:subp_opt=foo'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.user_options['subp:subp_opt'].value, 'foo')
          self.wipe()
  
          # c_args value should be parsed with shlex
          self.init(testdir, extra_args=['-Dc_args=foo bar "one two"'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.compiler_options['c_args'].value, ['foo', 'bar', 'one two'])
          self.setconf('-Dc_args="foo bar" one two')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.compiler_options['c_args'].value, ['foo bar', 'one', 'two'])
          self.wipe()
  
          # Setting a 2nd time the same option should override the first value
          try:
              self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar',
                                             '-Dbuildtype=plain', '-Dbuildtype=release',
                                             '-Db_sanitize=address', '-Db_sanitize=thread',
                                             '-Dc_args=foo', '-Dc_args=bar'])
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['bindir'].value, 'bar')
              self.assertEqual(obj.builtins['buildtype'].value, 'release')
              self.assertEqual(obj.base_options['b_sanitize'].value, 'thread')
              self.assertEqual(obj.compiler_options['c_args'].value, ['bar'])
              self.setconf(['--bindir=bar', '--bindir=foo',
                            '-Dbuildtype=release', '-Dbuildtype=plain',
                            '-Db_sanitize=thread', '-Db_sanitize=address',
                            '-Dc_args=bar', '-Dc_args=foo'])
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['bindir'].value, 'foo')
              self.assertEqual(obj.builtins['buildtype'].value, 'plain')
              self.assertEqual(obj.base_options['b_sanitize'].value, 'address')
              self.assertEqual(obj.compiler_options['c_args'].value, ['foo'])
              self.wipe()
          except KeyError:
              # Ignore KeyError, it happens on CI for compilers that does not
              # support b_sanitize. We have to test with a base option because
              # they used to fail this test with Meson 0.46 an earlier versions.
              pass
  
      def test_feature_check_usage_subprojects(self):
          testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects')
          out = self.init(testdir)
          # Parent project warns correctly
          self.assertRegex(out, "WARNING: Project targetting '>=0.45'.*'0.47.0': dict")
          # Subprojects warn correctly
          self.assertRegex(out, r"\|WARNING: Project targetting '>=0.40'.*'0.44.0': disabler")
          self.assertRegex(out, r"\|WARNING: Project targetting '!=0.40'.*'0.44.0': disabler")
          # Subproject has a new-enough meson_version, no warning
          self.assertNotRegex(out, "WARNING: Project targetting.*Python")
          # Ensure a summary is printed in the subproject and the outer project
          self.assertRegex(out, r"\|WARNING: Project specifies a minimum meson_version '>=0.40'")
          self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}")
          self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'")
          self.assertRegex(out, " * 0.47.0: {'dict'}")
  
      def test_configure_file_warnings(self):
          testdir = os.path.join(self.common_test_dir, "14 configure file")
          out = self.init(testdir)
          self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
          self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*")
          self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
          self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in")
          # Warnings for configuration files that are overwritten.
          self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites")
          self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites")
          self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites")
          # No warnings about empty configuration data objects passed to files with substitutions
          self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in")
          self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in")
          with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f:
              self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */')
          with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f:
              self.assertEqual(f.read().strip(), b'')
          self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict")
  
      def test_dirs(self):
          with tempfile.TemporaryDirectory() as containing:
              with tempfile.TemporaryDirectory(dir=containing) as srcdir:
                  mfile = os.path.join(srcdir, 'meson.build')
                  of = open(mfile, 'w')
                  of.write("project('foobar', 'c')\n")
                  of.close()
                  pc = subprocess.run(self.setup_command,
                                      cwd=srcdir,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.DEVNULL)
                  self.assertIn(b'Must specify at least one directory name', pc.stdout)
                  with tempfile.TemporaryDirectory(dir=srcdir) as builddir:
                      subprocess.run(self.setup_command,
                                     check=True,
                                     cwd=builddir,
                                     stdout=subprocess.DEVNULL,
                                     stderr=subprocess.DEVNULL)
  
      def get_opts_as_dict(self):
          result = {}
          for i in self.introspect('--buildoptions'):
              result[i['name']] = i['value']
          return result
  
      def test_buildtype_setting(self):
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          self.init(testdir)
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'debug')
          self.assertEqual(opts['debug'], True)
          self.setconf('-Ddebug=false')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['debug'], False)
          self.assertEqual(opts['buildtype'], 'plain')
          self.assertEqual(opts['optimization'], '0')
  
          # Setting optimizations to 3 should cause buildtype
          # to go to release mode.
          self.setconf('-Doptimization=3')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'release')
          self.assertEqual(opts['debug'], False)
          self.assertEqual(opts['optimization'], '3')
  
          # Going to debug build type should reset debugging
          # and optimization
          self.setconf('-Dbuildtype=debug')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'debug')
          self.assertEqual(opts['debug'], True)
          self.assertEqual(opts['optimization'], '0')
  
  
  class FailureTests(BasePlatformTests):
      '''
      Tests that test failure conditions. Build files here should be dynamically
      generated and static tests should go into `test cases/failing*`.
      This is useful because there can be many ways in which a particular
      function can fail, and creating failing tests for all of them is tedious
      and slows down testing.
      '''
      dnf = "[Dd]ependency.*not found"
      nopkg = '[Pp]kg-config not found'
  
      def setUp(self):
          super().setUp()
          self.srcdir = os.path.realpath(tempfile.mkdtemp())
          self.mbuild = os.path.join(self.srcdir, 'meson.build')
  
      def tearDown(self):
          super().tearDown()
          windows_proof_rmtree(self.srcdir)
  
      def assertMesonRaises(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents raises
          a error message matching regex @match.
          '''
          if langs is None:
              langs = []
          with open(self.mbuild, 'w') as f:
              f.write("project('failure test', 'c', 'cpp'")
              if meson_version:
                  f.write(", meson_version: '{}'".format(meson_version))
              f.write(")\n")
              for lang in langs:
                  f.write("add_languages('{}', required : false)\n".format(lang))
              f.write(contents)
          # Force tracebacks so we can detect them properly
          os.environ['MESON_FORCE_BACKTRACE'] = '1'
          with self.assertRaisesRegex(MesonException, match, msg=contents):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(self.srcdir, extra_args=extra_args, inprocess=True)
  
      def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None):
          if langs is None:
              langs = []
          with open(self.mbuild, 'w') as f:
              f.write("project('output test', 'c', 'cpp'")
              if meson_version:
                  f.write(", meson_version: '{}'".format(meson_version))
              f.write(")\n")
              for lang in langs:
                  f.write("add_languages('{}', required : false)\n".format(lang))
              f.write(contents)
          # Run in-process for speed and consistency with assertMesonRaises
          return self.init(self.srcdir, extra_args=extra_args, inprocess=True)
  
      def assertMesonOutputs(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents outputs
          something that matches regex @match.
          '''
          out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
          self.assertRegex(out, match)
  
      def assertMesonDoesNotOutput(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents does not output
          something that matches regex @match.
          '''
          out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
          self.assertNotRegex(out, match)
  
      @skipIfNoPkgconfig
      def test_dependency(self):
          if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0:
              raise unittest.SkipTest('zlib not found with pkg-config')
          a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"),
               ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"),
               ("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"),
               ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"),
               ("dependency('zlib', method : 1)", "[Mm]ethod.*string"),
               ("dependency('zlibfail')", self.dnf),)
          for contents, match in a:
              self.assertMesonRaises(contents, match)
  
      def test_apple_frameworks_dependency(self):
          if not is_osx():
              raise unittest.SkipTest('only run on macOS')
          self.assertMesonRaises("dependency('appleframeworks')",
                                 "requires at least one module")
  
      def test_sdl2_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('sdl2-config'):
              raise unittest.SkipTest('sdl2-config found')
          self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf)
          if shutil.which('pkg-config'):
              errmsg = self.dnf
          else:
              errmsg = self.nopkg
          self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", errmsg)
  
      def test_gnustep_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('gnustep-config'):
              raise unittest.SkipTest('gnustep-config found')
          self.assertMesonRaises("dependency('gnustep')",
                                 "(requires a Objc compiler|{})".format(self.dnf),
                                 langs = ['objc'])
  
      def test_wx_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('wx-config-3.0') or shutil.which('wx-config'):
              raise unittest.SkipTest('wx-config or wx-config-3.0 found')
          self.assertMesonRaises("dependency('wxwidgets')", self.dnf)
          self.assertMesonOutputs("dependency('wxwidgets', required : false)",
                                  "Dependency .*WxWidgets.* found: .*NO.*")
  
      def test_wx_dependency(self):
          if not shutil.which('wx-config-3.0') and not shutil.which('wx-config'):
              raise unittest.SkipTest('Neither wx-config nor wx-config-3.0 found')
          self.assertMesonRaises("dependency('wxwidgets', modules : 1)",
                                 "module argument is not a string")
  
      def test_llvm_dependency(self):
          self.assertMesonRaises("dependency('llvm', modules : 'fail')",
                                 "(required.*fail|{})".format(self.dnf))
  
      def test_boost_notfound_dependency(self):
          # Can be run even if Boost is found or not
          self.assertMesonRaises("dependency('boost', modules : 1)",
                                 "module.*not a string")
          self.assertMesonRaises("dependency('boost', modules : 'fail')",
                                 "(fail.*not found|{})".format(self.dnf))
  
      def test_boost_BOOST_ROOT_dependency(self):
          # Test BOOST_ROOT; can be run even if Boost is found or not
          os.environ['BOOST_ROOT'] = 'relative/path'
          self.assertMesonRaises("dependency('boost')",
                                 "(BOOST_ROOT.*absolute|{})".format(self.dnf))
  
      def test_dependency_invalid_method(self):
          code = '''zlib_dep = dependency('zlib', required : false)
          zlib_dep.get_configtool_variable('foo')
          '''
          self.assertMesonRaises(code, "'zlib' is not a config-tool dependency")
          code = '''zlib_dep = dependency('zlib', required : false)
          dep = declare_dependency(dependencies : zlib_dep)
          dep.get_pkgconfig_variable('foo')
          '''
          self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal")
          code = '''zlib_dep = dependency('zlib', required : false)
          dep = declare_dependency(dependencies : zlib_dep)
          dep.get_configtool_variable('foo')
          '''
          self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal")
  
      def test_objc_cpp_detection(self):
          '''
          Test that when we can't detect objc or objcpp, we fail gracefully.
          '''
          env = get_fake_env('', self.builddir, self.prefix)
          try:
              env.detect_objc_compiler(False)
              env.detect_objcpp_compiler(False)
          except EnvironmentException:
              code = "add_languages('objc')\nadd_languages('objcpp')"
              self.assertMesonRaises(code, "Unknown compiler")
              return
          raise unittest.SkipTest("objc and objcpp found, can't test detection failure")
  
      def test_subproject_variables(self):
          '''
          Test that:
          1. The correct message is outputted when a not-required dep is not
             found and the fallback subproject is also not found.
          2. A not-found not-required dep with a fallback subproject outputs the
             correct message when the fallback subproject is found but the
             variable inside it is not.
          3. A fallback dependency is found from the subproject parsed in (2)
          4. A not-required fallback dependency is not found because the
             subproject failed to parse.
          '''
          tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables')
          out = self.init(tdir, inprocess=True)
          self.assertRegex(out, r"Couldn't use fallback subproject "
                           "in.*subprojects.*nosubproj.*for the dependency.*somedep")
          self.assertRegex(out, r'Dependency.*somenotfounddep.*from subproject.*'
                           'subprojects.*somesubproj.*found:.*NO')
          self.assertRegex(out, r'Dependency.*zlibproxy.*from subproject.*'
                           'subprojects.*somesubproj.*found:.*YES.*(cached)')
          self.assertRegex(out, r'Couldn\'t use fallback subproject in '
                           '.*subprojects.*failingsubproj.*for the dependency.*somedep')
  
      def test_exception_exit_status(self):
          '''
          Test exit status on python exception
          '''
          tdir = os.path.join(self.unit_test_dir, '21 exit status')
          os.environ['MESON_UNIT_TEST'] = '1'
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(tdir, inprocess=False)
          self.assertEqual(cm.exception.returncode, 2)
          self.wipe()
  
      def test_dict_requires_key_value_pairs(self):
          self.assertMesonRaises("dict = {3, 'foo': 'bar'}",
                                 'Only key:value pairs are valid in dict construction.')
          self.assertMesonRaises("{'foo': 'bar', 3}",
                                 'Only key:value pairs are valid in dict construction.')
  
      def test_dict_forbids_duplicate_keys(self):
          self.assertMesonRaises("dict = {'a': 41, 'a': 42}",
                                 'Duplicate dictionary key: a.*')
  
      def test_dict_forbids_integer_key(self):
          self.assertMesonRaises("dict = {3: 'foo'}",
                                 'Key must be a string.*')
  
      def test_using_too_recent_feature(self):
          # Here we use a dict, which was introduced in 0.47.0
          self.assertMesonOutputs("dict = {}",
                                  ".*WARNING.*Project targetting.*but.*",
                                  meson_version='>= 0.46.0')
  
      def test_using_recent_feature(self):
          # Same as above, except the meson version is now appropriate
          self.assertMesonDoesNotOutput("dict = {}",
                                        ".*WARNING.*Project targetting.*but.*",
                                        meson_version='>= 0.47')
  
      def test_using_too_recent_feature_dependency(self):
          self.assertMesonOutputs("dependency('pcap', required: false)",
                                  ".*WARNING.*Project targetting.*but.*",
                                  meson_version='>= 0.41.0')
  
      def test_vcs_tag_featurenew_build_always_stale(self):
          'https://github.com/mesonbuild/meson/issues/3904'
          vcs_tag = '''version_data = configuration_data()
          version_data.set('PROJVER', '@VCS_TAG@')
          vf = configure_file(output : 'version.h.in', configuration: version_data)
          f = vcs_tag(input : vf, output : 'version.h')
          '''
          msg = '.*WARNING:.*feature.*build_always_stale.*custom_target.*'
          self.assertMesonDoesNotOutput(vcs_tag, msg, meson_version='>=0.43')
  
      def test_missing_subproject_not_required_and_required(self):
          self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
                                 "sub2 = subproject('not-found-subproject', required: true)",
                                 """.*Subproject "subprojects/not-found-subproject" required but not found.*""")
  
      def test_get_variable_on_not_found_project(self):
          self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
                                 "sub1.get_variable('naaa')",
                                 """Subproject "subprojects/not-found-subproject" disabled can't get_variable on it.""")
  
  
  class WindowsTests(BasePlatformTests):
      '''
      Tests that should run on Cygwin, MinGW, and MSVC
      '''
      def setUp(self):
          super().setUp()
          self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows')
  
      @unittest.skipIf(is_cygwin(), 'Test only applicable to Windows')
      def test_find_program(self):
          '''
          Test that Windows-specific edge-cases in find_program are functioning
          correctly. Cannot be an ordinary test because it involves manipulating
          PATH to point to a directory with Python scripts.
          '''
          testdir = os.path.join(self.platform_test_dir, '8 find program')
          # Find `cmd` and `cmd.exe`
          prog1 = ExternalProgram('cmd')
          self.assertTrue(prog1.found(), msg='cmd not found')
          prog2 = ExternalProgram('cmd.exe')
          self.assertTrue(prog2.found(), msg='cmd.exe not found')
          self.assertPathEqual(prog1.get_path(), prog2.get_path())
          # Find cmd with an absolute path that's missing the extension
          cmd_path = prog2.get_path()[:-4]
          prog = ExternalProgram(cmd_path)
          self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path))
          # Finding a script with no extension inside a directory works
          prog = ExternalProgram(os.path.join(testdir, 'test-script'))
          self.assertTrue(prog.found(), msg='test-script not found')
          # Finding a script with an extension inside a directory works
          prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py'))
          self.assertTrue(prog.found(), msg='test-script-ext.py not found')
          # Finding a script in PATH w/o extension works and adds the interpreter
          os.environ['PATH'] += os.pathsep + testdir
          prog = ExternalProgram('test-script-ext')
          self.assertTrue(prog.found(), msg='test-script-ext not found in PATH')
          self.assertPathEqual(prog.get_command()[0], python_command[0])
          self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
          # Finding a script in PATH with extension works and adds the interpreter
          prog = ExternalProgram('test-script-ext.py')
          self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH')
          self.assertPathEqual(prog.get_command()[0], python_command[0])
          self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
  
      def test_ignore_libs(self):
          '''
          Test that find_library on libs that are to be ignored returns an empty
          array of arguments. Must be a unit test because we cannot inspect
          ExternalLibraryHolder from build files.
          '''
          testdir = os.path.join(self.platform_test_dir, '1 basic')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          if cc.id != 'msvc':
              raise unittest.SkipTest('Not using MSVC')
          # To force people to update this test, and also test
          self.assertEqual(set(cc.ignore_libs), {'c', 'm', 'pthread', 'dl', 'rt'})
          for l in cc.ignore_libs:
              self.assertEqual(cc.find_library(l, env, []), [])
  
      def test_rc_depends_files(self):
          testdir = os.path.join(self.platform_test_dir, '5 resources')
  
          # resource compiler depfile generation is not yet implemented for msvc
          env = get_fake_env(testdir, self.builddir, self.prefix)
          depfile_works = env.detect_c_compiler(False).get_id() != 'msvc'
  
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Test compile_resources(depend_file:)
          # Changing mtime of sample.ico should rebuild prog
          self.utime(os.path.join(testdir, 'res', 'sample.ico'))
          self.assertRebuiltTarget('prog')
          # Test depfile generation by compile_resources
          # Changing mtime of resource.h should rebuild myres.rc and then prog
          if depfile_works:
              self.utime(os.path.join(testdir, 'inc', 'resource', 'resource.h'))
              self.assertRebuiltTarget('prog')
          self.wipe()
  
          if depfile_works:
              testdir = os.path.join(self.platform_test_dir, '12 resources with custom targets')
              self.init(testdir)
              self.build()
              # Immediately rebuilding should not do anything
              self.assertBuildIsNoop()
              # Changing mtime of resource.h should rebuild myres_1.rc and then prog_1
              self.utime(os.path.join(testdir, 'res', 'resource.h'))
              self.assertRebuiltTarget('prog_1')
  
  class DarwinTests(BasePlatformTests):
      '''
      Tests that should run on macOS
      '''
      def setUp(self):
          super().setUp()
          self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx')
  
      def test_apple_bitcode(self):
          '''
          Test that -fembed-bitcode is correctly added while compiling and
          -bitcode_bundle is added while linking when b_bitcode 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, '4 shared')
          # Try with bitcode enabled
          out = self.init(testdir, extra_args='-Db_bitcode=true')
          # Warning was printed
          self.assertRegex(out, 'WARNING:.*b_bitcode')
          # Compiler options were added
          compdb = self.get_compdb()
          self.assertIn('-fembed-bitcode', compdb[0]['command'])
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          # Linker options were added
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
          self.assertIsNotNone(m, msg=contents)
          # Try with bitcode disabled
          self.setconf('-Db_bitcode=false')
          # Regenerate build
          self.build()
          compdb = self.get_compdb()
          self.assertNotIn('-fembed-bitcode', compdb[0]['command'])
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
          self.assertIsNone(m, msg=contents)
  
      def test_apple_bitcode_modules(self):
          '''
          Same as above, just for shared_module()
          '''
          testdir = os.path.join(self.common_test_dir, '153 shared module resolving symbol in executable')
          # Ensure that it builds even with bitcode enabled
          self.init(testdir, extra_args='-Db_bitcode=true')
          self.build()
          self.run_tests()
  
      def _get_darwin_versions(self, fname):
          fname = os.path.join(self.builddir, fname)
          out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True)
          m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1])
          self.assertIsNotNone(m, msg=out)
          return m.groups()
  
      def test_library_versioning(self):
          '''
          Ensure that compatibility_version and current_version are set correctly
          '''
          testdir = os.path.join(self.platform_test_dir, '2 library versions')
          self.init(testdir)
          self.build()
          targets = {}
          for t in self.introspect('--targets'):
              targets[t['name']] = t['filename']
          self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0'))
          self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1'))
  
  
  class LinuxlikeTests(BasePlatformTests):
      '''
      Tests that should run on Linux, macOS, and *BSD
      '''
      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, '25 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.
          '''
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('PIC not relevant')
  
          testdir = os.path.join(self.common_test_dir, '3 static')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertIn('-fPIC', compdb[0]['command'])
          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, '48 pkgconfig-gen')
          self.init(testdir)
          env = get_fake_env(testdir, self.builddir, self.prefix)
          kwargs = {'required': True, 'silent': True}
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          foo_dep = PkgConfigDependency('libfoo', env, kwargs)
          self.assertTrue(foo_dep.found())
          self.assertEqual(foo_dep.get_version(), '1.0')
          self.assertIn('-lfoo', foo_dep.get_link_args())
          self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar')
          self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data')
  
      def test_pkgconfig_gen_deps(self):
          '''
          Test that generated pkg-config files correctly handle dependencies
          '''
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen')
          self.init(testdir)
          privatedir1 = self.privatedir
  
          self.new_builddir()
          os.environ['PKG_CONFIG_LIBDIR'] = privatedir1
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen', 'dependencies')
          self.init(testdir)
          privatedir2 = self.privatedir
  
          os.environ['PKG_CONFIG_LIBDIR'] = os.pathsep.join([privatedir1, privatedir2])
          cmd = ['pkg-config', 'dependency-test']
  
          out = self._run(cmd + ['--print-requires']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed']))
  
          out = self._run(cmd + ['--print-requires-private']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libfoo >= 1.0']))
  
          out = self._run(cmd + ['--cflags-only-other']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-DCUSTOM']))
  
          out = self._run(cmd + ['--libs-only-l', '--libs-only-other']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
                                                '-llibmain', '-llibexposed']))
  
          out = self._run(cmd + ['--libs-only-l', '--libs-only-other', '--static']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
                                                '-llibmain', '-llibexposed',
                                                '-llibinternal', '-lcustom2',
                                                '-lfoo']))
  
          cmd = ['pkg-config', 'requires-test']
          out = self._run(cmd + ['--print-requires']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
  
          cmd = ['pkg-config', 'requires-private-test']
          out = self._run(cmd + ['--print-requires-private']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
  
      def test_pkg_unfound(self):
          testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig')
          self.init(testdir)
          with open(os.path.join(self.privatedir, 'somename.pc')) as f:
              pcfile = f.read()
          self.assertFalse('blub_blob_blib' in pcfile)
  
      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
          '''
          if not shutil.which('valac'):
              raise unittest.SkipTest('valac not installed.')
          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('GLib.Thread.vala'):
                  continue
              elif each['file'].endswith('retcode.c'):
                  c_command = each['command']
              else:
                  m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file'])
                  raise AssertionError(m)
          self.assertIsNotNone(vala_command)
          self.assertIsNotNone(c_command)
          # -w suppresses all warnings, should be there in Vala but not in C
          self.assertIn(" -w ", vala_command)
          self.assertNotIn(" -w ", c_command)
          # -Wall enables all warnings, should be there in C but not in Vala
          self.assertNotIn(" -Wall ", vala_command)
          self.assertIn(" -Wall ", c_command)
          # -Werror converts warnings to errors, should always be there since it's
          # injected by an unrelated piece of code and the project has werror=true
          self.assertIn(" -Werror ", vala_command)
          self.assertIn(" -Werror ", c_command)
  
      @skipIfNoPkgconfig
      def test_qt5dependency_pkgconfig_detection(self):
          '''
          Test that qt4 and qt5 detection with pkgconfig works.
          '''
          # Verify Qt4 or Qt5 can be found with pkg-config
          qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore'])
          qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core'])
          if qt4 != 0 or qt5 != 0:
              raise unittest.SkipTest('Qt not found with pkg-config')
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir, ['-Dmethod=pkg-config'])
          # Confirm that the dependency was found with pkg-config
          mesonlog = self.get_meson_log()
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt4 \(modules: Core\) found: YES .*, `pkg-config`\n')
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt5 \(modules: Core\) found: YES .*, `pkg-config`\n')
  
      def test_generate_gir_with_address_sanitizer(self):
          if is_cygwin():
              raise unittest.SkipTest('asan not available on Cygwin')
  
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          self.init(testdir, ['-Db_sanitize=address', '-Db_lundef=false'])
          self.build()
  
      def test_qt5dependency_qmake_detection(self):
          '''
          Test that qt5 detection with qmake works. This can't be an ordinary
          test case because it involves setting the environment.
          '''
          # Verify that qmake is for Qt5
          if not shutil.which('qmake-qt5'):
              if not shutil.which('qmake'):
                  raise unittest.SkipTest('QMake not found')
              output = subprocess.getoutput('qmake --version')
              if 'Qt version 5' not in output:
                  raise unittest.SkipTest('Qmake found, but it is not for Qt 5.')
          # Disable pkg-config codepath and force searching with qmake/qmake-qt5
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir, ['-Dmethod=qmake'])
          # Confirm that the dependency was found with qmake
          mesonlog = self.get_meson_log()
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n')
  
      def _test_soname_impl(self, libpath, install):
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames')
  
          testdir = os.path.join(self.unit_test_dir, '1 soname')
          self.init(testdir)
          self.build()
          if install:
              self.install()
  
          # File without aliases set.
          nover = os.path.join(libpath, 'libnover.so')
          self.assertPathExists(nover)
          self.assertFalse(os.path.islink(nover))
          self.assertEqual(get_soname(nover), 'libnover.so')
          self.assertEqual(len(glob(nover[:-3] + '*')), 1)
  
          # File with version set
          verset = os.path.join(libpath, 'libverset.so')
          self.assertPathExists(verset + '.4.5.6')
          self.assertEqual(os.readlink(verset), 'libverset.so.4')
          self.assertEqual(get_soname(verset), 'libverset.so.4')
          self.assertEqual(len(glob(verset[:-3] + '*')), 3)
  
          # File with soversion set
          soverset = os.path.join(libpath, 'libsoverset.so')
          self.assertPathExists(soverset + '.1.2.3')
          self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
  
          # File with version and soversion set to same values
          settosame = os.path.join(libpath, 'libsettosame.so')
          self.assertPathExists(settosame + '.7.8.9')
          self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
  
          # File with version and soversion set to different values
          bothset = os.path.join(libpath, 'libbothset.so')
          self.assertPathExists(bothset + '.1.2.3')
          self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
          self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
  
      def test_soname(self):
          self._test_soname_impl(self.builddir, False)
  
      def test_installed_soname(self):
          libdir = self.installdir + os.path.join(self.prefix, self.libdir)
          self._test_soname_impl(libdir, True)
  
      def test_compiler_check_flags_order(self):
          '''
          Test that compiler check flags override all other flags. This can't be
          an ordinary test case because it needs the environment to be set.
          '''
          Oflag = '-O3'
          os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag
          testdir = os.path.join(self.common_test_dir, '40 has function')
          self.init(testdir)
          cmds = self.get_meson_log_compiler_checks()
          for cmd in cmds:
              if cmd[0] == 'ccache':
                  cmd = cmd[1:]
              # Verify that -I flags from the `args` kwarg are first
              # This is set in the '40 has function' test case
              self.assertEqual(cmd[1], '-I/tmp')
              # Verify that -O3 set via the environment is overridden by -O0
              Oargs = [arg for arg in cmd if arg.startswith('-O')]
              self.assertEqual(Oargs, [Oflag, '-O0'])
  
      def _test_stds_impl(self, testdir, compiler, p):
          lang_std = p + '_std'
          # Check that all the listed -std=xxx options for this compiler work
          # just fine when used
          for v in compiler.get_options()[lang_std].choices:
              if (compiler.get_id() == 'clang' and '17' in v and
                  (version_compare(compiler.version, '<5.0.0') or
                   (compiler.compiler_type == mesonbuild.compilers.CompilerType.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
                  continue
              if (compiler.get_id() == 'clang' and '2a' in v and
                  (version_compare(compiler.version, '<6.0.0') or
                   (compiler.compiler_type == mesonbuild.compilers.CompilerType.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
                  continue
              if (compiler.get_id() == 'gcc' and '2a' in v and version_compare(compiler.version, '<8.0.0')):
                  continue
              std_opt = '{}={}'.format(lang_std, v)
              self.init(testdir, ['-D' + std_opt])
              cmd = self.get_compdb()[0]['command']
              if v != 'none':
                  cmd_std = " -std={} ".format(v)
                  self.assertIn(cmd_std, cmd)
              try:
                  self.build()
              except:
                  print('{} was {!r}'.format(lang_std, v))
                  raise
              self.wipe()
          # Check that an invalid std option in CFLAGS/CPPFLAGS fails
          # Needed because by default ICC ignores invalid options
          cmd_std = '-std=FAIL'
          env_flags = p.upper() + 'FLAGS'
          os.environ[env_flags] = cmd_std
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          qcmd_std = " {} ".format(cmd_std)
          self.assertIn(qcmd_std, cmd)
          with self.assertRaises(subprocess.CalledProcessError,
                                 msg='{} should have failed'.format(qcmd_std)):
              self.build()
  
      def test_compiler_c_stds(self):
          '''
          Test that C stds specified for this compiler can all be used. Can't be
          an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          self._test_stds_impl(testdir, cc, 'c')
  
      def test_compiler_cpp_stds(self):
          '''
          Test that C++ stds specified for this compiler can all be used. Can't
          be an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '2 cpp')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cpp = env.detect_cpp_compiler(False)
          self._test_stds_impl(testdir, cpp, 'cpp')
  
      def test_unity_subproj(self):
          testdir = os.path.join(self.common_test_dir, '46 subproject')
          self.init(testdir, extra_args='--unity=subprojects')
          self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@simpletest@exe/simpletest-unity.c'))
          self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@sublib@sha/sublib-unity.c'))
          self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c'))
          self.build()
  
      def test_installed_modes(self):
          '''
          Test that files installed by these tests have the correct permissions.
          Can't be an ordinary test because our installed_files.txt is very basic.
          '''
          # Test file modes
          testdir = os.path.join(self.common_test_dir, '12 data')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'etc', 'etcfile.dat')
          found_mode = stat.filemode(os.stat(f).st_mode)
          want_mode = 'rw------T'
          self.assertEqual(want_mode, found_mode[1:])
  
          f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-sr-x'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
              self.assertEqual(0, statf.st_gid)
  
          f = os.path.join(self.installdir, 'usr', 'share', 'progname',
                           'fileobject_datafile.dat')
          orig = os.path.join(testdir, 'fileobject_datafile.dat')
          statf = os.stat(f)
          statorig = os.stat(orig)
          found_mode = stat.filemode(statf.st_mode)
          orig_mode = stat.filemode(statorig.st_mode)
          self.assertEqual(orig_mode[1:], found_mode[1:])
          self.assertEqual(os.getuid(), statf.st_uid)
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_gid)
  
          self.wipe()
          # Test directory modes
          testdir = os.path.join(self.common_test_dir, '63 install subdir')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-x--t'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
  
      def test_installed_modes_extended(self):
          '''
          Test that files are installed with correct permissions using install_mode.
          '''
          testdir = os.path.join(self.common_test_dir, '196 install_mode')
          self.init(testdir)
          self.build()
          self.install()
  
          for fsobj, want_mode in [
                  ('bin', 'drwxr-x---'),
                  ('bin/runscript.sh', '-rwxr-sr-x'),
                  ('bin/trivialprog', '-rwxr-sr-x'),
                  ('include', 'drwxr-x---'),
                  ('include/config.h', '-rw-rwSr--'),
                  ('include/rootdir.h', '-r--r--r-T'),
                  ('lib', 'drwxr-x---'),
                  ('lib/libstat.a', '-rw---Sr--'),
                  ('share', 'drwxr-x---'),
                  ('share/man', 'drwxr-x---'),
                  ('share/man/man1', 'drwxr-x---'),
                  ('share/man/man1/foo.1.gz', '-r--r--r-T'),
                  ('share/sub1', 'drwxr-x---'),
                  ('share/sub1/second.dat', '-rwxr-x--t'),
                  ('subdir', 'drwxr-x---'),
                  ('subdir/data.dat', '-rw-rwSr--'),
          ]:
              f = os.path.join(self.installdir, 'usr', *fsobj.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (fsobj, want_mode, found_mode)))
          # Ensure that introspect --installed works on all types of files
          # FIXME: also verify the files list
          self.introspect('--installed')
  
      def test_install_umask(self):
          '''
          Test that files are installed with correct permissions using default
          install umask of 022, regardless of the umask at time the worktree
          was checked out or the build was executed.
          '''
          # Copy source tree to a temporary directory and change permissions
          # there to simulate a checkout with umask 002.
          orig_testdir = os.path.join(self.unit_test_dir, '26 install umask')
          # Create a new testdir under tmpdir.
          tmpdir = os.path.realpath(tempfile.mkdtemp())
          self.addCleanup(windows_proof_rmtree, tmpdir)
          testdir = os.path.join(tmpdir, '26 install umask')
          # Copy the tree using shutil.copyfile, which will use the current umask
          # instead of preserving permissions of the old tree.
          save_umask = os.umask(0o002)
          self.addCleanup(os.umask, save_umask)
          shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile)
          # Preserve the executable status of subdir/sayhello though.
          os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775)
          self.init(testdir)
          # Run the build under a 027 umask now.
          os.umask(0o027)
          self.build()
          # And keep umask 027 for the install step too.
          self.install()
  
          for executable in [
                  'bin/prog',
                  'share/subdir/sayhello',
          ]:
              f = os.path.join(self.installdir, 'usr', *executable.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = '-rwxr-xr-x'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (executable, want_mode, found_mode)))
  
          for directory in [
                  'usr',
                  'usr/bin',
                  'usr/include',
                  'usr/share',
                  'usr/share/man',
                  'usr/share/man/man1',
                  'usr/share/subdir',
          ]:
              f = os.path.join(self.installdir, *directory.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = 'drwxr-xr-x'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected directory %s to have mode %s but found %s instead.' %
                                    (directory, want_mode, found_mode)))
  
          for datafile in [
                  'include/sample.h',
                  'share/datafile.cat',
                  'share/file.dat',
                  'share/man/man1/prog.1.gz',
                  'share/subdir/datafile.dog',
          ]:
              f = os.path.join(self.installdir, 'usr', *datafile.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = '-rw-r--r--'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (datafile, want_mode, found_mode)))
  
      def test_cpp_std_override(self):
          testdir = os.path.join(self.unit_test_dir, '6 std override')
          self.init(testdir)
          compdb = self.get_compdb()
          for i in compdb:
              if 'prog03' in i['file']:
                  c03_comp = i['command']
              if 'prog11' in i['file']:
                  c11_comp = i['command']
              if 'progp' in i['file']:
                  plain_comp = i['command']
          self.assertNotEqual(len(plain_comp), 0)
          self.assertIn('-std=c++03', c03_comp)
          self.assertNotIn('-std=c++11', c03_comp)
          self.assertIn('-std=c++11', c11_comp)
          self.assertNotIn('-std=c++03', c11_comp)
          self.assertNotIn('-std=c++03', plain_comp)
          self.assertNotIn('-std=c++11', plain_comp)
          # Now werror
          self.assertIn('-Werror', plain_comp)
          self.assertNotIn('-Werror', c03_comp)
  
      def test_run_installed(self):
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('LD_LIBRARY_PATH and RPATH not applicable')
  
          testdir = os.path.join(self.unit_test_dir, '7 run installed')
          self.init(testdir)
          self.build()
          self.install()
          installed_exe = os.path.join(self.installdir, 'usr/bin/prog')
          installed_libdir = os.path.join(self.installdir, 'usr/foo')
          installed_lib = os.path.join(installed_libdir, 'libfoo.so')
          self.assertTrue(os.path.isfile(installed_exe))
          self.assertTrue(os.path.isdir(installed_libdir))
          self.assertTrue(os.path.isfile(installed_lib))
          # Must fail when run without LD_LIBRARY_PATH to ensure that
          # rpath has been properly stripped rather than pointing to the builddir.
          self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0)
          # When LD_LIBRARY_PATH is set it should start working.
          # For some reason setting LD_LIBRARY_PATH in os.environ fails
          # when all tests are run (but works when only this test is run),
          # but doing this explicitly works.
          env = os.environ.copy()
          env['LD_LIBRARY_PATH'] = installed_libdir
          self.assertEqual(subprocess.call(installed_exe, env=env), 0)
          # Ensure that introspect --installed works
          installed = self.introspect('--installed')
          for v in installed.values():
              self.assertTrue('prog' in v or 'foo' in v)
  
      @skipIfNoPkgconfig
      def test_order_of_l_arguments(self):
          testdir = os.path.join(self.unit_test_dir, '8 -L -l order')
          os.environ['PKG_CONFIG_PATH'] = testdir
          self.init(testdir)
          # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders
          # the flags before returning them to -Lfoo -Lbar -lfoo -lbar
          # but pkgconf seems to not do that. Sigh. Support both.
          expected_order = [('-L/me/first', '-lfoo1'),
                            ('-L/me/second', '-lfoo2'),
                            ('-L/me/first', '-L/me/second'),
                            ('-lfoo1', '-lfoo2'),
                            ('-L/me/second', '-L/me/third'),
                            ('-L/me/third', '-L/me/fourth',),
                            ('-L/me/third', '-lfoo3'),
                            ('-L/me/fourth', '-lfoo4'),
                            ('-lfoo3', '-lfoo4'),
                            ]
          with open(os.path.join(self.builddir, 'build.ninja')) as ifile:
              for line in ifile:
                  if expected_order[0][0] in line:
                      for first, second in expected_order:
                          self.assertLess(line.index(first), line.index(second))
                      return
          raise RuntimeError('Linker entries not found in the Ninja file.')
  
      def test_introspect_dependencies(self):
          '''
          Tests that mesonintrospect --dependencies returns expected output.
          '''
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          self.init(testdir)
          glib_found = False
          gobject_found = False
          deps = self.introspect('--dependencies')
          self.assertIsInstance(deps, list)
          for dep in deps:
              self.assertIsInstance(dep, dict)
              self.assertIn('name', dep)
              self.assertIn('compile_args', dep)
              self.assertIn('link_args', dep)
              if dep['name'] == 'glib-2.0':
                  glib_found = True
              elif dep['name'] == 'gobject-2.0':
                  gobject_found = True
          self.assertTrue(glib_found)
          self.assertTrue(gobject_found)
          if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0:
              raise unittest.SkipTest('glib >= 2.56.2 needed for the rest')
          targets = self.introspect('--targets')
          docbook_target = None
          for t in targets:
              if t['name'] == 'generated-gdbus-docbook':
                  docbook_target = t
                  break
          self.assertIsInstance(docbook_target, dict)
          ifile = self.introspect(['--target-files', 'generated-gdbus-docbook@cus'])[0]
          self.assertEqual(t['filename'], 'gdbus/generated-gdbus-doc-' + ifile)
  
      def test_build_rpath(self):
          if is_cygwin():
              raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
          testdir = os.path.join(self.unit_test_dir, '10 build_rpath')
          self.init(testdir)
          self.build()
          # C program RPATH
          build_rpath = get_rpath(os.path.join(self.builddir, 'prog'))
          self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
          self.install()
          install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog'))
          self.assertEqual(install_rpath, '/baz')
          # C++ program RPATH
          build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx'))
          self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
          self.install()
          install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx'))
          self.assertEqual(install_rpath, 'baz')
  
      def test_pch_with_address_sanitizer(self):
          if is_cygwin():
              raise unittest.SkipTest('asan not available on Cygwin')
  
          testdir = os.path.join(self.common_test_dir, '13 pch')
          self.init(testdir, ['-Db_sanitize=address'])
          self.build()
          compdb = self.get_compdb()
          for i in compdb:
              self.assertIn("-fsanitize=address", i["command"])
  
      def test_coverage(self):
          gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
          if not gcovr_exe:
              raise unittest.SkipTest('gcovr not found')
          if not shutil.which('genhtml') and not gcovr_new_rootdir:
              raise unittest.SkipTest('genhtml not found and gcovr is too old')
          if 'clang' in os.environ.get('CC', ''):
              # We need to use llvm-cov instead of gcovr with clang
              raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!')
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          self.init(testdir, ['-Db_coverage=true'])
          self.build()
          self.run_tests()
          self.run_target('coverage-html')
  
      def test_cross_find_program(self):
          testdir = os.path.join(self.unit_test_dir, '11 cross prog')
          crossfile = tempfile.NamedTemporaryFile(mode='w')
          print(os.path.join(testdir, 'some_cross_tool.py'))
          crossfile.write('''[binaries]
  c = '/usr/bin/cc'
  ar = '/usr/bin/ar'
  strip = '/usr/bin/ar'
  sometool.py = ['{0}']
  someothertool.py = '{0}'
  
  [properties]
  
  [host_machine]
  system = 'linux'
  cpu_family = 'arm'
  cpu = 'armv7' # Not sure if correct.
  endian = 'little'
  '''.format(os.path.join(testdir, 'some_cross_tool.py')))
          crossfile.flush()
          self.meson_cross_file = crossfile.name
          self.init(testdir)
  
      def test_reconfigure(self):
          testdir = os.path.join(self.unit_test_dir, '13 reconfigure')
          self.init(testdir, ['-Db_coverage=true'], default_args=False)
          self.build('reconfigure')
  
      def test_vala_generated_source_buildir_inside_source_tree(self):
          '''
          Test that valac outputs generated C files in the expected location when
          the builddir is a subdir of the source tree.
          '''
          if not shutil.which('valac'):
              raise unittest.SkipTest('valac not installed.')
  
          testdir = os.path.join(self.vala_test_dir, '8 generated sources')
          newdir = os.path.join(self.builddir, 'srctree')
          shutil.copytree(testdir, newdir)
          testdir = newdir
          # New builddir
          builddir = os.path.join(testdir, 'subdir/_build')
          os.makedirs(builddir, exist_ok=True)
          self.change_builddir(builddir)
          self.init(testdir)
          self.build()
  
      def test_old_gnome_module_codepaths(self):
          '''
          A lot of code in the GNOME module is conditional on the version of the
          glib tools that are installed, and breakages in the old code can slip
          by once the CI has a newer glib version. So we force the GNOME module
          to pretend that it's running on an ancient glib so the fallback code is
          also tested.
          '''
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          os.environ['MESON_UNIT_TEST_PRETEND_GLIB_OLD'] = "1"
          mesonbuild.modules.gnome.native_glib_version = '2.20'
          self.init(testdir, inprocess=True)
          self.build()
          mesonbuild.modules.gnome.native_glib_version = None
  
      @skipIfNoPkgconfig
      def test_pkgconfig_usage(self):
          testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency')
          testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee')
          if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'],
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL) != 0:
              raise unittest.SkipTest('Glib 2.0 dependency not available.')
          with tempfile.TemporaryDirectory() as tempdirname:
              self.init(testdir1, ['--prefix=' + tempdirname, '--libdir=lib'], default_args=False)
              self.install(use_destdir=False)
              shutil.rmtree(self.builddir)
              os.mkdir(self.builddir)
              pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
              self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc')))
              lib_dir = os.path.join(tempdirname, 'lib')
              os.environ['PKG_CONFIG_PATH'] = pkg_dir
              # Private internal libraries must not leak out.
              pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'])
              self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.')
              # Dependencies must not leak to cflags when building only a shared library.
              pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'])
              self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.')
              # Test that the result is usable.
              self.init(testdir2)
              self.build()
              myenv = os.environ.copy()
              myenv['LD_LIBRARY_PATH'] = lib_dir
              if is_cygwin():
                  bin_dir = os.path.join(tempdirname, 'bin')
                  myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH']
              self.assertTrue(os.path.isdir(lib_dir))
              test_exe = os.path.join(self.builddir, 'pkguser')
              self.assertTrue(os.path.isfile(test_exe))
              subprocess.check_call(test_exe, env=myenv)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_internal_libraries(self):
          '''
          '''
          with tempfile.TemporaryDirectory() as tempdirname:
              # build library
              testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries')
              testdirlib = os.path.join(testdirbase, 'lib')
              self.init(testdirlib, extra_args=['--prefix=' + tempdirname,
                                                '--libdir=lib',
                                                '--default-library=static'], default_args=False)
              self.build()
              self.install(use_destdir=False)
  
              # build user of library
              pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
              os.environ['PKG_CONFIG_PATH'] = pkg_dir
              self.new_builddir()
              self.init(os.path.join(testdirbase, 'app'))
              self.build()
  
      @skipIfNoPkgconfig
      def test_pkgconfig_formatting(self):
          testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format')
          self.init(testdir)
          myenv = os.environ.copy()
          myenv['PKG_CONFIG_PATH'] = self.privatedir
          stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv)
          deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething']
          if is_windows() or is_cygwin() or is_osx():
              # On Windows, libintl is a separate library
              deps.append(b'-lintl')
          self.assertEqual(set(deps), set(stdo.split()))
  
      def test_deterministic_dep_order(self):
          '''
          Test that the dependencies are always listed in a deterministic order.
          '''
          testdir = os.path.join(self.unit_test_dir, '43 dep order')
          self.init(testdir)
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if 'build myexe:' in line or 'build myexe.exe:' in line:
                      self.assertIn('liblib1.a liblib2.a', line)
                      return
          raise RuntimeError('Could not find the build rule')
  
      def test_deterministic_rpath_order(self):
          '''
          Test that the rpaths are always listed in a deterministic order.
          '''
          if is_cygwin():
              raise unittest.SkipTest('rpath are not used on Cygwin')
          testdir = os.path.join(self.unit_test_dir, '42 rpath order')
          self.init(testdir)
          if is_osx():
              rpathre = re.compile('-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2')
          else:
              rpathre = re.compile('-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2')
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if '-rpath' in line:
                      self.assertRegex(line, rpathre)
                      return
          raise RuntimeError('Could not find the rpath')
  
      def test_override_with_exe_dep(self):
          '''
          Test that we produce the correct dependencies when a program is overridden with an executable.
          '''
          testdir = os.path.join(self.common_test_dir, '202 override with exe')
          self.init(testdir)
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if 'main1.c:' in line or 'main2.c:' in line:
                      self.assertIn('| subprojects/sub/foobar', line)
  
      @skipIfNoPkgconfig
      def test_usage_external_library(self):
          '''
          Test that uninstalled usage of an external library (from the system or
          PkgConfigDependency) works. On macOS, this workflow works out of the
          box. On Linux, BSDs, Windows, etc, you need to set extra arguments such
          as LD_LIBRARY_PATH, etc, so this test is skipped.
  
          The system library is found with cc.find_library() and pkg-config deps.
          '''
          oldprefix = self.prefix
          # Install external library so we can find it
          testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library')
          # install into installdir without using DESTDIR
          installdir = self.installdir
          self.prefix = installdir
          self.init(testdir)
          self.prefix = oldprefix
          self.build()
          self.install(use_destdir=False)
          ## New builddir for the consumer
          self.new_builddir()
          os.environ['LIBRARY_PATH'] = os.path.join(installdir, self.libdir)
          os.environ['PKG_CONFIG_PATH'] = os.path.join(installdir, self.libdir, 'pkgconfig')
          testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library')
          # install into installdir without using DESTDIR
          self.prefix = self.installdir
          self.init(testdir)
          self.prefix = oldprefix
          self.build()
          # test uninstalled
          self.run_tests()
          if not is_osx():
              # Rest of the workflow only works on macOS
              return
          # test running after installation
          self.install(use_destdir=False)
          prog = os.path.join(self.installdir, 'bin', 'prog')
          self._run([prog])
          out = self._run(['otool', '-L', prog])
          self.assertNotIn('@rpath', out)
          ## New builddir for testing that DESTDIR is not added to install_name
          self.new_builddir()
          # install into installdir with DESTDIR
          self.init(testdir)
          self.build()
          # test running after installation
          self.install()
          prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog')
          lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib')
          for f in prog, lib:
              out = self._run(['otool', '-L', f])
              # Ensure that the otool output does not contain self.installdir
              self.assertNotRegex(out, self.installdir + '.*dylib ')
  
      def install_subdir_invalid_symlinks(self, testdir, subdir_path):
          '''
          Test that installation of broken symlinks works fine.
          https://github.com/mesonbuild/meson/issues/3914
          '''
          testdir = os.path.join(self.common_test_dir, testdir)
          subdir = os.path.join(testdir, subdir_path)
          curdir = os.getcwd()
          os.chdir(subdir)
          # Can't distribute broken symlinks in the source tree because it breaks
          # the creation of zipapps. Create it dynamically and run the test by
          # hand.
          src = '../../nonexistent.txt'
          os.symlink(src, 'invalid-symlink.txt')
          try:
              self.init(testdir)
              self.build()
              self.install()
              install_path = subdir_path.split(os.path.sep)[-1]
              link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt')
              self.assertTrue(os.path.islink(link), msg=link)
              self.assertEqual(src, os.readlink(link))
              self.assertFalse(os.path.isfile(link), msg=link)
          finally:
              os.remove(os.path.join(subdir, 'invalid-symlink.txt'))
              os.chdir(curdir)
  
      def test_install_subdir_symlinks(self):
          self.install_subdir_invalid_symlinks('63 install subdir', os.path.join('sub', 'sub1'))
  
      def test_install_subdir_symlinks_with_default_umask(self):
          self.install_subdir_invalid_symlinks('196 install_mode', 'sub2')
  
      def test_install_subdir_symlinks_with_default_umask_and_mode(self):
          self.install_subdir_invalid_symlinks('196 install_mode', 'sub1')
  
  
  class LinuxCrossArmTests(BasePlatformTests):
      '''
      Tests that cross-compilation to Linux/ARM works
      '''
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt')
  
      def test_cflags_cross_environment_pollution(self):
          '''
          Test that the CFLAGS environment variable does not pollute the cross
          environment. 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')
          os.environ['CFLAGS'] = '-DBUILD_ENVIRONMENT_ONLY'
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command'])
  
      def test_cross_file_overrides_always_args(self):
          '''
          Test that $lang_args in cross files always override get_always_args().
          Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some
          architectures such as some Android versions and Raspbian.
          https://github.com/mesonbuild/meson/issues/3049
          https://github.com/mesonbuild/meson/issues/3089
          '''
          testdir = os.path.join(self.unit_test_dir, '33 cross file overrides always args')
          self.meson_cross_file = os.path.join(testdir, 'ubuntu-armhf-overrides.txt')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS')
          self.build()
  
  class LinuxCrossMingwTests(BasePlatformTests):
      '''
      Tests that cross-compilation to Windows/MinGW works
      '''
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt')
  
      def test_exe_wrapper_behaviour(self):
          '''
          Test that an exe wrapper that isn't found doesn't cause compiler sanity
          checks and compiler checks to fail, but causes configure to fail if it
          requires running a cross-built executable (custom_target or run_target)
          and causes the tests to be skipped if they are run.
          '''
          testdir = os.path.join(self.unit_test_dir, '36 exe_wrapper behaviour')
          # Configures, builds, and tests fine by default
          self.init(testdir)
          self.build()
          self.run_tests()
          self.wipe()
          os.mkdir(self.builddir)
          # Change cross file to use a non-existing exe_wrapper and it should fail
          self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt')
          # Force tracebacks so we can detect them properly
          os.environ['MESON_FORCE_BACKTRACE'] = '1'
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(testdir, extra_args='-Drun-target=false', inprocess=True)
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(testdir, extra_args='-Dcustom-target=false', inprocess=True)
          self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'])
          self.build()
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.run_tests(inprocess=True)
  
  
  class PythonTests(BasePlatformTests):
      '''
      Tests that verify compilation of python extension modules
      '''
      def test_versions(self):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name))
  
          testdir = os.path.join(self.src_root, 'test cases', 'unit', '39 python extmodule')
  
          # No python version specified, this will use meson's python
          self.init(testdir)
          self.build()
          self.run_tests()
          self.wipe()
  
          # When specifying a known name, (python2 / python3) the module
          # will also try 'python' as a fallback and use it if the major
          # version matches
          try:
              self.init(testdir, ['-Dpython=python2'])
              self.build()
              self.run_tests()
          except unittest.SkipTest:
              # python2 is not necessarily installed on the test machine,
              # if it is not, or the python headers can't be found, the test
              # will raise MESON_SKIP_TEST, we could check beforehand what version
              # of python is available, but it's a bit of a chicken and egg situation,
              # as that is the job of the module, so we just ask for forgiveness rather
              # than permission.
              pass
  
          self.wipe()
  
          for py in ('pypy', 'pypy3'):
              try:
                  self.init(testdir, ['-Dpython=%s' % py])
              except unittest.SkipTest:
                  # Same as above, pypy2 and pypy3 are not expected to be present
                  # on the test system, the test project only raises in these cases
                  continue
  
              # We have a pypy, this is expected to work
              self.build()
              self.run_tests()
              self.wipe()
  
          # The test is configured to error out with MESON_SKIP_TEST
          # in case it could not find python
          with self.assertRaises(unittest.SkipTest):
              self.init(testdir, ['-Dpython=not-python'])
          self.wipe()
  
          # While dir is an external command on both Windows and Linux,
          # it certainly isn't python
          with self.assertRaises(unittest.SkipTest):
              self.init(testdir, ['-Dpython=dir'])
          self.wipe()
  
  
  class RewriterTests(unittest.TestCase):
  
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.testroot = os.path.realpath(tempfile.mkdtemp())
          self.rewrite_command = python_command + [os.path.join(src_root, 'mesonrewriter.py')]
          self.tmpdir = os.path.realpath(tempfile.mkdtemp())
          self.workdir = os.path.join(self.tmpdir, 'foo')
          self.test_dir = os.path.join(src_root, 'test cases/rewrite')
  
      def tearDown(self):
          windows_proof_rmtree(self.tmpdir)
  
      def read_contents(self, fname):
          with open(os.path.join(self.workdir, fname)) as f:
              return f.read()
  
      def check_effectively_same(self, mainfile, truth):
          mf = self.read_contents(mainfile)
          t = self.read_contents(truth)
          # Rewriting is not guaranteed to do a perfect job of
          # maintaining whitespace.
          self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
  
      def prime(self, dirname):
          shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
  
      def test_basic(self):
          self.prime('1 basic')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'removed.txt')
          subprocess.check_call(self.rewrite_command + ['add',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'added.txt')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'removed.txt')
  
      def test_subdir(self):
          self.prime('2 subdirs')
          top = self.read_contents('meson.build')
          s2 = self.read_contents('sub2/meson.build')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=something',
                                                        '--filename=second.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
          self.assertEqual(top, self.read_contents('meson.build'))
          self.assertEqual(s2, self.read_contents('sub2/meson.build'))
  
  
  def unset_envs():
      # For unit tests we must fully control all command lines
      # so that there are no unexpected changes coming from the
      # environment, for example when doing a package build.
      varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.cflags_mapping.values())
      for v in varnames:
          if v in os.environ:
              del os.environ[v]
  
  def should_run_cross_arm_tests():
      return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
  
  def should_run_cross_mingw_tests():
      return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin())
  
  if __name__ == '__main__':
      unset_envs()
      cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 'PythonTests']
      if not is_windows():
          cases += ['LinuxlikeTests']
          if should_run_cross_arm_tests():
              cases += ['LinuxCrossArmTests']
          if should_run_cross_mingw_tests():
              cases += ['LinuxCrossMingwTests']
      if is_windows() or is_cygwin():
          cases += ['WindowsTests']
      if is_osx():
          cases += ['DarwinTests']
  
      unittest.main(defaultTest=cases, buffer=True)
  
files: run_unittests.py
files: #!/usr/bin/env python3
  # Copyright 2016-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  import stat
  import shlex
  import subprocess
  import re, json
  import tempfile
  import textwrap
  import os
  import shutil
  import unittest
  import platform
  from itertools import chain
  from unittest import mock
  from configparser import ConfigParser
  from glob import glob
  from pathlib import (PurePath, Path)
  
  import mesonbuild.mlog
  import mesonbuild.compilers
  import mesonbuild.environment
  import mesonbuild.mesonlib
  import mesonbuild.coredata
  import mesonbuild.modules.gnome
  from mesonbuild.interpreter import Interpreter, ObjectHolder
  from mesonbuild.mesonlib import (
      is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd,
      windows_proof_rmtree, python_command, version_compare,
      BuildDirLock, Version
  )
  from mesonbuild.environment import detect_ninja
  from mesonbuild.mesonlib import MesonException, EnvironmentException
  from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
  import mesonbuild.modules.pkgconfig
  
  from run_tests import exe_suffix, get_fake_env, get_meson_script
  from run_tests import get_builddir_target_args, get_backend_commands, Backend
  from run_tests import ensure_backend_detects_changes, run_configure_inprocess
  from run_tests import run_mtest_inprocess
  from run_tests import FakeBuild, FakeCompilerOptions
  
  def get_dynamic_section_entry(fname, entry):
      if is_cygwin() or is_osx():
              raise unittest.SkipTest('Test only applicable to ELF platforms')
  
      try:
          raw_out = subprocess.check_output(['readelf', '-d', fname],
                                            universal_newlines=True)
      except FileNotFoundError:
          # FIXME: Try using depfixer.py:Elf() as a fallback
          raise unittest.SkipTest('readelf not found')
      pattern = re.compile(entry + r': \[(.*?)\]')
      for line in raw_out.split('\n'):
          m = pattern.search(line)
          if m is not None:
              return m.group(1)
      return None # The file did not contain the specified entry.
  
  def get_soname(fname):
      return get_dynamic_section_entry(fname, 'soname')
  
  def get_rpath(fname):
      return get_dynamic_section_entry(fname, r'(?:rpath|runpath)')
  
  def is_tarball():
      if not os.path.isdir('docs'):
          return True
      return False
  
  def is_ci():
      if 'TRAVIS' in os.environ or 'APPVEYOR' in os.environ:
          return True
      return False
  
  def _git_init(project_dir):
      subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL)
      subprocess.check_call(['git', 'config',
                             'user.name', 'Author Person'], cwd=project_dir)
      subprocess.check_call(['git', 'config',
                             'user.email', 'teh_coderz@example.com'], cwd=project_dir)
      subprocess.check_call('git add *', cwd=project_dir, shell=True,
                            stdout=subprocess.DEVNULL)
      subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir,
                            stdout=subprocess.DEVNULL)
  
  def skipIfNoPkgconfig(f):
      '''
      Skip this test if no pkg-config is found, unless we're on Travis or
      Appveyor CI.  This allows users to run our test suite without having
      pkg-config installed on, f.ex., macOS, while ensuring that our CI does not
      silently skip the test because of misconfiguration.
  
      Note: Yes, we provide pkg-config even while running Windows CI
      '''
      def wrapped(*args, **kwargs):
          if not is_ci() and shutil.which('pkg-config') is None:
              raise unittest.SkipTest('pkg-config not found')
          return f(*args, **kwargs)
      return wrapped
  
  class PatchModule:
      '''
      Fancy monkey-patching! Whee! Can't use mock.patch because it only
      patches in the local namespace.
      '''
      def __init__(self, func, name, impl):
          self.func = func
          assert(isinstance(name, str))
          self.func_name = name
          self.old_impl = None
          self.new_impl = impl
  
      def __enter__(self):
          self.old_impl = self.func
          exec('{} = self.new_impl'.format(self.func_name))
  
      def __exit__(self, *args):
          exec('{} = self.old_impl'.format(self.func_name))
  
  
  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
          cc = mesonbuild.compilers.CCompiler([], 'fake', False)
          # Test that bad initialization fails
          self.assertRaises(TypeError, cargsfunc, [])
          self.assertRaises(TypeError, cargsfunc, [], [])
          self.assertRaises(TypeError, cargsfunc, cc, [], [])
          # Test that empty initialization works
          a = cargsfunc(cc)
          self.assertEqual(a, [])
          # Test that list initialization works
          a = cargsfunc(['-I.', '-I..'], cc)
          self.assertEqual(a, ['-I.', '-I..'])
          # Test that there is no de-dup on initialization
          self.assertEqual(cargsfunc(['-I.', '-I.'], cc), ['-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 yields 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'])
          # Test that adding to a list with old args does nothing
          a = ['-Ibar', '-Ibaz', '-Ifoo'] + a
          self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
  
          ## Test that adding libraries works
          l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo'])
          # Adding a library and a libpath appends both correctly
          l += ['-Lbardir', '-lbar']
          self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
          # Adding the same library again does nothing
          l += ['-lbar']
          self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
  
          ## Test that 'direct' append and extend works
          l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo'])
          # Direct-adding a library and a libpath appends both correctly
          l.extend_direct(['-Lbardir', '-lbar'])
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar'])
          # Direct-adding the same library again still adds it
          l.append_direct('-lbar')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar'])
          # Direct-adding with absolute path deduplicates
          l.append_direct('/libbaz.a')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
          # Adding libbaz again does nothing
          l.append_direct('/libbaz.a')
          self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
  
      def test_compiler_args_class_gnuld(self):
          cargsfunc = mesonbuild.compilers.CompilerArgs
          ## Test --start/end-group
          gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', 0, False)
          ## Test that 'direct' append and extend works
          l = cargsfunc(gcc, ['-Lfoodir', '-lfoo'])
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
          # Direct-adding a library and a libpath appends both correctly
          l.extend_direct(['-Lbardir', '-lbar'])
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group'])
          # Direct-adding the same library again still adds it
          l.append_direct('-lbar')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group'])
          # Direct-adding with absolute path deduplicates
          l.append_direct('/libbaz.a')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
          # Adding libbaz again does nothing
          l.append_direct('/libbaz.a')
          self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
          # Adding a non-library argument doesn't include it in the group
          l += ['-Lfoo', '-Wl,--export-dynamic']
          self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic'])
          # -Wl,-lfoo is detected as a library and gets added to the group
          l.append('-Wl,-ldl')
          self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group'])
  
      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']
          self.assertRaises(ME, substfunc, cmd, d)
          # 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']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
          # Two inputs, two outputs
          outputs = ['dir/out.c', 'dir/out2.c']
          ret = dictfunc(inputs, outputs)
          d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
               '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
               '@OUTDIR@': 'dir'}
          # Check dictionary
          self.assertEqual(ret, d)
          # Check substitutions
          cmd = ['some', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), cmd)
          cmd = ['@OUTPUT@', 'ordinary', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
          cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
          self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
          cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
          self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
          # Many inputs, can't use @INPUT@ like this
          cmd = ['@INPUT@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough inputs
          cmd = ['@INPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Not enough outputs
          cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
          # Many outputs, can't use @OUTPUT@ like this
          cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
          self.assertRaises(ME, substfunc, cmd, d)
  
      def test_needs_exe_wrapper_override(self):
          config = ConfigParser()
          config['binaries'] = {
              'c': '\'/usr/bin/gcc\'',
          }
          config['host_machine'] = {
              'system': '\'linux\'',
              'cpu_family': '\'arm\'',
              'cpu': '\'armv7\'',
              'endian': '\'little\'',
          }
          # Can not be used as context manager because we need to
          # open it a second time and this is not possible on
          # Windows.
          configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
          configfilename = configfile.name
          config.write(configfile)
          configfile.flush()
          configfile.close()
          detected_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper()
          os.unlink(configfilename)
  
          desired_value = not detected_value
          config['properties'] = {
              'needs_exe_wrapper': 'true' if desired_value else 'false'
          }
  
          configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
          configfilename = configfile.name
          config.write(configfile)
          configfile.close()
          forced_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper()
          os.unlink(configfilename)
  
          self.assertEqual(forced_value, desired_value)
  
      def test_listify(self):
          listify = mesonbuild.mesonlib.listify
          # Test sanity
          self.assertEqual([1], listify(1))
          self.assertEqual([], listify([]))
          self.assertEqual([1], listify([1]))
          # Test flattening
          self.assertEqual([1, 2, 3], listify([1, [2, 3]]))
          self.assertEqual([1, 2, 3], listify([1, [2, [3]]]))
          self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
          # Test flattening and unholdering
          holder1 = ObjectHolder(1)
          holder3 = ObjectHolder(3)
          self.assertEqual([holder1], listify(holder1))
          self.assertEqual([holder1], listify([holder1]))
          self.assertEqual([holder1, 2], listify([holder1, 2]))
          self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]]))
          self.assertEqual([1], listify(holder1, unholder=True))
          self.assertEqual([1], listify([holder1], unholder=True))
          self.assertEqual([1, 2], listify([holder1, 2], unholder=True))
          self.assertEqual([1, 2, 3], listify([holder1, 2, [holder3]], unholder=True))
          # Unholding doesn't work recursively when not flattening
          self.assertEqual([1, [2], [holder3]], listify([holder1, [2], [holder3]], unholder=True, flatten=False))
  
      def test_extract_as_list(self):
          extract = mesonbuild.mesonlib.extract_as_list
          # Test sanity
          kwargs = {'sources': [1, 2, 3]}
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
          self.assertEqual(kwargs, {'sources': [1, 2, 3]})
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True))
          self.assertEqual(kwargs, {})
          # Test unholding
          holder3 = ObjectHolder(3)
          kwargs = {'sources': [1, 2, holder3]}
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True))
          self.assertEqual(kwargs, {'sources': [1, 2, holder3]})
          self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True, pop=True))
          self.assertEqual(kwargs, {})
          # Test listification
          kwargs = {'sources': [1, 2, 3], 'pch_sources': [4, 5, 6]}
          self.assertEqual([[1, 2, 3], [4, 5, 6]], extract(kwargs, 'sources', 'pch_sources'))
  
      def test_pkgconfig_module(self):
  
          class Mock:
              pass
  
          mock = Mock()
          mock.pcdep = Mock()
          mock.pcdep.name = "some_name"
          mock.version_reqs = []
  
          # pkgconfig dependency as lib
          deps = mesonbuild.modules.pkgconfig.DependenciesHelper("thislib")
          deps.add_pub_libs([mock])
          self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
  
          # pkgconfig dependency as requires
          deps = mesonbuild.modules.pkgconfig.DependenciesHelper("thislib")
          deps.add_pub_reqs([mock])
          self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
  
      def _test_all_naming(self, cc, env, patterns, platform):
          shr = patterns[platform]['shared']
          stc = patterns[platform]['static']
          p = cc.get_library_naming(env, 'shared')
          self.assertEqual(p, shr)
          p = cc.get_library_naming(env, 'static')
          self.assertEqual(p, stc)
          p = cc.get_library_naming(env, 'static-shared')
          self.assertEqual(p, stc + shr)
          p = cc.get_library_naming(env, 'shared-static')
          self.assertEqual(p, shr + stc)
          p = cc.get_library_naming(env, 'default')
          self.assertEqual(p, shr + stc)
          # Test find library by mocking up openbsd
          if platform != 'openbsd':
              return
          with tempfile.TemporaryDirectory() as tmpdir:
              with open(os.path.join(tmpdir, 'libfoo.so.6.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.5.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.54.0'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.66a.0b'), 'w') as f:
                  f.write('')
              with open(os.path.join(tmpdir, 'libfoo.so.70.0.so.1'), 'w') as f:
                  f.write('')
              found = cc.find_library_real('foo', env, [tmpdir], '', 'default')
              self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
  
      def test_find_library_patterns(self):
          '''
          Unit test for the library search patterns used by find_library()
          '''
          unix_static = ('lib{}.a', '{}.a')
          msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib')
          # This is the priority list of pattern matching for library searching
          patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*'),
                                  'static': unix_static},
                      'linux': {'shared': ('lib{}.so', '{}.so'),
                                'static': unix_static},
                      'darwin': {'shared': ('lib{}.dylib', '{}.dylib'),
                                 'static': unix_static},
                      'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll',
                                            'lib{}.dll.a', '{}.dll', '{}.dll.a'),
                                 'static': ('cyg{}.a',) + unix_static},
                      'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'),
                                       'static': msvc_static},
                      'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll',
                                                   '{}.dll.a', '{}.lib', '{}.dll'),
                                        'static': msvc_static}}
          env = get_fake_env('', '', '')
          cc = env.detect_c_compiler(False)
          if is_osx():
              self._test_all_naming(cc, env, patterns, 'darwin')
          elif is_cygwin():
              self._test_all_naming(cc, env, patterns, 'cygwin')
          elif is_windows():
              if cc.get_id() == 'msvc':
                  self._test_all_naming(cc, env, patterns, 'windows-msvc')
              else:
                  self._test_all_naming(cc, env, patterns, 'windows-mingw')
          else:
              self._test_all_naming(cc, env, patterns, 'linux')
              # Mock OpenBSD since we don't have tests for it
              true = lambda x, y: True
              if not is_openbsd():
                  with PatchModule(mesonbuild.compilers.c.for_openbsd,
                                   'mesonbuild.compilers.c.for_openbsd', true):
                      self._test_all_naming(cc, env, patterns, 'openbsd')
              else:
                  self._test_all_naming(cc, env, patterns, 'openbsd')
              with PatchModule(mesonbuild.compilers.c.for_darwin,
                               'mesonbuild.compilers.c.for_darwin', true):
                  self._test_all_naming(cc, env, patterns, 'darwin')
              with PatchModule(mesonbuild.compilers.c.for_cygwin,
                               'mesonbuild.compilers.c.for_cygwin', true):
                  self._test_all_naming(cc, env, patterns, 'cygwin')
              with PatchModule(mesonbuild.compilers.c.for_windows,
                               'mesonbuild.compilers.c.for_windows', true):
                  self._test_all_naming(cc, env, patterns, 'windows-mingw')
              cc.id = 'msvc'
              with PatchModule(mesonbuild.compilers.c.for_windows,
                               'mesonbuild.compilers.c.for_windows', true):
                  self._test_all_naming(cc, env, patterns, 'windows-msvc')
  
      def test_pkgconfig_parse_libs(self):
          '''
          Unit test for parsing of pkg-config output to search for libraries
  
          https://github.com/mesonbuild/meson/issues/3951
          '''
          with tempfile.TemporaryDirectory() as tmpdir:
              pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
              env = get_fake_env('', '', '')
              compiler = env.detect_c_compiler(False)
              env.coredata.compilers = {'c': compiler}
              env.coredata.compiler_options['c_link_args'] = FakeCompilerOptions()
              p1 = Path(tmpdir) / '1'
              p2 = Path(tmpdir) / '2'
              p1.mkdir()
              p2.mkdir()
              # libfoo.a is in one prefix
              (p1 / 'libfoo.a').open('w').close()
              # libbar.a is in both prefixes
              (p1 / 'libbar.a').open('w').close()
              (p2 / 'libbar.a').open('w').close()
              # Ensure that we never statically link to these
              (p1 / 'libpthread.a').open('w').close()
              (p1 / 'libm.a').open('w').close()
              (p1 / 'libc.a').open('w').close()
              (p1 / 'libdl.a').open('w').close()
              (p1 / 'librt.a').open('w').close()
  
              def fake_call_pkgbin(self, args, env=None):
                  if '--libs' not in args:
                      return 0, ''
                  if args[0] == 'foo':
                      return 0, '-L{} -lfoo -L{} -lbar'.format(p2.as_posix(), p1.as_posix())
                  if args[0] == 'bar':
                      return 0, '-L{} -lbar'.format(p2.as_posix())
                  if args[0] == 'internal':
                      return 0, '-L{} -lpthread -lm -lc -lrt -ldl'.format(p1.as_posix())
  
              old_call = PkgConfigDependency._call_pkgbin
              old_check = PkgConfigDependency.check_pkgconfig
              PkgConfigDependency._call_pkgbin = fake_call_pkgbin
              PkgConfigDependency.check_pkgconfig = lambda x: pkgbin
              # Test begins
              kwargs = {'required': True, 'silent': True}
              foo_dep = PkgConfigDependency('foo', env, kwargs)
              self.assertEqual(foo_dep.get_link_args(),
                               [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
              bar_dep = PkgConfigDependency('bar', env, kwargs)
              self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
              internal_dep = PkgConfigDependency('internal', env, kwargs)
              if compiler.get_id() == 'msvc':
                  self.assertEqual(internal_dep.get_link_args(), [])
              else:
                  link_args = internal_dep.get_link_args()
                  for link_arg in link_args:
                      for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
                          self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args)
              # Test ends
              PkgConfigDependency._call_pkgbin = old_call
              PkgConfigDependency.check_pkgconfig = old_check
              # Reset dependency class to ensure that in-process configure doesn't mess up
              PkgConfigDependency.pkgbin_cache = {}
              PkgConfigDependency.class_pkgbin = None
  
      def test_version_compare(self):
          comparefunc = mesonbuild.mesonlib.version_compare_many
          for (a, b, result) in [
                  ('0.99.beta19', '>= 0.99.beta14', True),
          ]:
              self.assertEqual(comparefunc(a, b)[0], result)
  
          for (a, b, result) in [
                  # examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison
                  ("1.0010", "1.9", 1),
                  ("1.05", "1.5", 0),
                  ("1.0", "1", 1),
                  ("2.50", "2.5", 1),
                  ("fc4", "fc.4", 0),
                  ("FC5", "fc4", -1),
                  ("2a", "2.0", -1),
                  ("1.0", "1.fc4", 1),
                  ("3.0.0_fc", "3.0.0.fc", 0),
                  # from RPM tests
                  ("1.0", "1.0", 0),
                  ("1.0", "2.0", -1),
                  ("2.0", "1.0", 1),
                  ("2.0.1", "2.0.1", 0),
                  ("2.0", "2.0.1", -1),
                  ("2.0.1", "2.0", 1),
                  ("2.0.1a", "2.0.1a", 0),
                  ("2.0.1a", "2.0.1", 1),
                  ("2.0.1", "2.0.1a", -1),
                  ("5.5p1", "5.5p1", 0),
                  ("5.5p1", "5.5p2", -1),
                  ("5.5p2", "5.5p1", 1),
                  ("5.5p10", "5.5p10", 0),
                  ("5.5p1", "5.5p10", -1),
                  ("5.5p10", "5.5p1", 1),
                  ("10xyz", "10.1xyz", -1),
                  ("10.1xyz", "10xyz", 1),
                  ("xyz10", "xyz10", 0),
                  ("xyz10", "xyz10.1", -1),
                  ("xyz10.1", "xyz10", 1),
                  ("xyz.4", "xyz.4", 0),
                  ("xyz.4", "8", -1),
                  ("8", "xyz.4", 1),
                  ("xyz.4", "2", -1),
                  ("2", "xyz.4", 1),
                  ("5.5p2", "5.6p1", -1),
                  ("5.6p1", "5.5p2", 1),
                  ("5.6p1", "6.5p1", -1),
                  ("6.5p1", "5.6p1", 1),
                  ("6.0.rc1", "6.0", 1),
                  ("6.0", "6.0.rc1", -1),
                  ("10b2", "10a1", 1),
                  ("10a2", "10b2", -1),
                  ("1.0aa", "1.0aa", 0),
                  ("1.0a", "1.0aa", -1),
                  ("1.0aa", "1.0a", 1),
                  ("10.0001", "10.0001", 0),
                  ("10.0001", "10.1", 0),
                  ("10.1", "10.0001", 0),
                  ("10.0001", "10.0039", -1),
                  ("10.0039", "10.0001", 1),
                  ("4.999.9", "5.0", -1),
                  ("5.0", "4.999.9", 1),
                  ("20101121", "20101121", 0),
                  ("20101121", "20101122", -1),
                  ("20101122", "20101121", 1),
                  ("2_0", "2_0", 0),
                  ("2.0", "2_0", 0),
                  ("2_0", "2.0", 0),
                  ("a", "a", 0),
                  ("a+", "a+", 0),
                  ("a+", "a_", 0),
                  ("a_", "a+", 0),
                  ("+a", "+a", 0),
                  ("+a", "_a", 0),
                  ("_a", "+a", 0),
                  ("+_", "+_", 0),
                  ("_+", "+_", 0),
                  ("_+", "_+", 0),
                  ("+", "_", 0),
                  ("_", "+", 0),
                  # other tests
                  ('0.99.beta19', '0.99.beta14', 1),
                  ("1.0.0", "2.0.0", -1),
                  (".0.0", "2.0.0", -1),
                  ("alpha", "beta", -1),
                  ("1.0", "1.0.0", -1),
                  ("2.456", "2.1000", -1),
                  ("2.1000", "3.111", -1),
                  ("2.001", "2.1", 0),
                  ("2.34", "2.34", 0),
                  ("6.1.2", "6.3.8", -1),
                  ("1.7.3.0", "2.0.0", -1),
                  ("2.24.51", "2.25", -1),
                  ("2.1.5+20120813+gitdcbe778", "2.1.5", 1),
                  ("3.4.1", "3.4b1", 1),
                  ("041206", "200090325", -1),
                  ("0.6.2+git20130413", "0.6.2", 1),
                  ("2.6.0+bzr6602", "2.6.0", 1),
                  ("2.6.0", "2.6b2", 1),
                  ("2.6.0+bzr6602", "2.6b2x", 1),
                  ("0.6.7+20150214+git3a710f9", "0.6.7", 1),
                  ("15.8b", "15.8.0.1", -1),
                  ("1.2rc1", "1.2.0", -1),
          ]:
              ver_a = Version(a)
              ver_b = Version(b)
              self.assertEqual(ver_a.__cmp__(ver_b), result)
              self.assertEqual(ver_b.__cmp__(ver_a), -result)
  
  @unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
  class DataTests(unittest.TestCase):
  
      def test_snippets(self):
          hashcounter = re.compile('^ *(#)+')
          snippet_dir = Path('docs/markdown/snippets')
          self.assertTrue(snippet_dir.is_dir())
          for f in snippet_dir.glob('*'):
              self.assertTrue(f.is_file())
              if f.parts[-1].endswith('~'):
                  continue
              if f.suffix == '.md':
                  in_code_block = False
                  with f.open() as snippet:
                      for line in snippet:
                          if line.startswith('    '):
                              continue
                          if line.startswith('```'):
                              in_code_block = not in_code_block
                          if in_code_block:
                              continue
                          m = re.match(hashcounter, line)
                          if m:
                              self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name)
                  self.assertFalse(in_code_block, 'Unclosed code block.')
              else:
                  if f.name != 'add_release_note_snippets_here':
                      self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name)
  
      def test_compiler_options_documented(self):
          '''
          Test that C and C++ compiler options and base options are documented in
          Builtin-Options.md. Only tests the default compiler for the current
          platform on the CI.
          '''
          md = None
          with open('docs/markdown/Builtin-options.md') as f:
              md = f.read()
          self.assertIsNotNone(md)
          env = get_fake_env('', '', '')
          # FIXME: Support other compilers
          cc = env.detect_c_compiler(False)
          cpp = env.detect_cpp_compiler(False)
          for comp in (cc, cpp):
              for opt in comp.get_options().keys():
                  self.assertIn(opt, md)
              for opt in comp.base_options:
                  self.assertIn(opt, md)
          self.assertNotIn('b_unknown', md)
  
      def test_cpu_families_documented(self):
          with open("docs/markdown/Reference-tables.md") as f:
              md = f.read()
          self.assertIsNotNone(md)
  
          sections = list(re.finditer(r"^## (.+)$", md, re.MULTILINE))
          for s1, s2 in zip(sections[::2], sections[1::2]):
              if s1.group(1) == "CPU families":
                  # Extract the content for this section
                  content = md[s1.end():s2.start()]
                  # Find the list entries
                  arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)]
                  # Drop the header
                  arches = set(arches[1:])
                  self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families))
  
      def test_markdown_files_in_sitemap(self):
          '''
          Test that each markdown files in docs/markdown is referenced in sitemap.txt
          '''
          with open("docs/sitemap.txt") as f:
              md = f.read()
          self.assertIsNotNone(md)
          toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE))
          markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md']
          exceptions = ['_Sidebar.md']
          for f in markdownfiles:
              if f not in exceptions:
                  self.assertIn(f, toc)
  
      def test_syntax_highlighting_files(self):
          '''
          Ensure that syntax highlighting files were updated for new functions in
          the global namespace in build files.
          '''
          env = get_fake_env('', '', '')
          interp = Interpreter(FakeBuild(env), mock=True)
          with open('data/syntax-highlighting/vim/syntax/meson.vim') as f:
              res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE)
              defined = set([a.strip() for a in res.group().split('\\')][1:])
              self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys())))
  
  
  class BasePlatformTests(unittest.TestCase):
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          src_root = os.path.join(os.getcwd(), src_root)
          self.src_root = src_root
          self.prefix = '/usr'
          self.libdir = 'lib'
          # Get the backend
          # FIXME: Extract this from argv?
          self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja'))
          self.meson_args = ['--backend=' + self.backend.name]
          self.meson_cross_file = None
          self.meson_command = python_command + [get_meson_script()]
          self.setup_command = self.meson_command + self.meson_args
          self.mconf_command = self.meson_command + ['configure']
          self.mintro_command = self.meson_command + ['introspect']
          self.wrap_command = self.meson_command + ['wrap']
          # Backend-specific build commands
          self.build_command, self.clean_command, self.test_command, self.install_command, \
              self.uninstall_command = get_backend_commands(self.backend)
          # Test directories
          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')
          # Misc stuff
          self.orig_env = os.environ.copy()
          if self.backend is Backend.ninja:
              self.no_rebuild_stdout = 'ninja: no work to do.'
          else:
              # VS doesn't have a stable output when no changes are done
              # XCode backend is untested with unit tests, help welcome!
              self.no_rebuild_stdout = 'UNKNOWN BACKEND {!r}'.format(self.backend.name)
  
          self.builddirs = []
          self.new_builddir()
  
      def change_builddir(self, newdir):
          self.builddir = newdir
          self.privatedir = os.path.join(self.builddir, 'meson-private')
          self.logdir = os.path.join(self.builddir, 'meson-logs')
          self.installdir = os.path.join(self.builddir, 'install')
          self.distdir = os.path.join(self.builddir, 'meson-dist')
          self.mtest_command = self.meson_command + ['test', '-C', self.builddir]
          self.builddirs.append(self.builddir)
  
      def new_builddir(self):
          # In case the directory is inside a symlinked directory, find the real
          # path otherwise we might not find the srcdir from inside the builddir.
          newdir = os.path.realpath(tempfile.mkdtemp())
          self.change_builddir(newdir)
  
      def _print_meson_log(self):
          log = os.path.join(self.logdir, 'meson-log.txt')
          if not os.path.isfile(log):
              print("{!r} doesn't exist".format(log))
              return
          with open(log, 'r', encoding='utf-8') as f:
              print(f.read())
  
      def tearDown(self):
          for path in self.builddirs:
              try:
                  windows_proof_rmtree(path)
              except FileNotFoundError:
                  pass
          os.environ.clear()
          os.environ.update(self.orig_env)
          super().tearDown()
  
      def _run(self, command, workdir=None):
          '''
          Run a command while printing the stdout and stderr to stdout,
          and also return a copy of it
          '''
          # If this call hangs CI will just abort. It is very hard to distinguish
          # between CI issue and test bug in that case. Set timeout and fail loud
          # instead.
          p = subprocess.run(command, stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT, env=os.environ.copy(),
                             universal_newlines=True, cwd=workdir, timeout=60 * 5)
          print(p.stdout)
          if p.returncode != 0:
              if 'MESON_SKIP_TEST' in p.stdout:
                  raise unittest.SkipTest('Project requested skipping.')
              raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout)
          return p.stdout
  
      def init(self, srcdir, extra_args=None, default_args=True, inprocess=False):
          self.assertPathExists(srcdir)
          if extra_args is None:
              extra_args = []
          if not isinstance(extra_args, list):
              extra_args = [extra_args]
          args = [srcdir, self.builddir]
          if default_args:
              args += ['--prefix', self.prefix,
                       '--libdir', self.libdir]
              if self.meson_cross_file:
                  args += ['--cross-file', self.meson_cross_file]
          self.privatedir = os.path.join(self.builddir, 'meson-private')
          if inprocess:
              try:
                  (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args)
                  if 'MESON_SKIP_TEST' in out:
                      raise unittest.SkipTest('Project requested skipping.')
                  if returncode != 0:
                      self._print_meson_log()
                      print('Stdout:\n')
                      print(out)
                      print('Stderr:\n')
                      print(err)
                      raise RuntimeError('Configure failed')
              except:
                  self._print_meson_log()
                  raise
              finally:
                  # Close log file to satisfy Windows file locking
                  mesonbuild.mlog.shutdown()
                  mesonbuild.mlog.log_dir = None
                  mesonbuild.mlog.log_file = None
          else:
              try:
                  out = self._run(self.setup_command + args + extra_args)
              except unittest.SkipTest:
                  raise unittest.SkipTest('Project requested skipping: ' + srcdir)
              except:
                  self._print_meson_log()
                  raise
          return out
  
      def build(self, target=None, extra_args=None):
          if extra_args is None:
              extra_args = []
          # Add arguments for building the target (if specified),
          # and using the build dir (if required, with VS)
          args = get_builddir_target_args(self.backend, self.builddir, target)
          return self._run(self.build_command + args + extra_args, workdir=self.builddir)
  
      def clean(self):
          dir_args = get_builddir_target_args(self.backend, self.builddir, None)
          self._run(self.clean_command + dir_args, workdir=self.builddir)
  
      def run_tests(self, inprocess=False):
          if not inprocess:
              self._run(self.test_command, workdir=self.builddir)
          else:
              run_mtest_inprocess(['-C', self.builddir])
  
      def install(self, *, use_destdir=True):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
          if use_destdir:
              os.environ['DESTDIR'] = self.installdir
          self._run(self.install_command, workdir=self.builddir)
  
      def uninstall(self):
          self._run(self.uninstall_command, workdir=self.builddir)
  
      def run_target(self, target):
          '''
          Run a Ninja target while printing the stdout and stderr to stdout,
          and also return a copy of it
          '''
          return self.build(target=target)
  
      def setconf(self, arg, will_build=True):
          if not isinstance(arg, list):
              arg = [arg]
          if will_build:
              ensure_backend_detects_changes(self.backend)
          self._run(self.mconf_command + arg + [self.builddir])
  
      def wipe(self):
          windows_proof_rmtree(self.builddir)
  
      def utime(self, f):
          ensure_backend_detects_changes(self.backend)
          os.utime(f)
  
      def get_compdb(self):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name))
          with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
              contents = json.load(ifile)
          # If Ninja is using .rsp files, generate them, read their contents, and
          # replace it as the command for all compile commands in the parsed json.
          if len(contents) > 0 and contents[0]['command'].endswith('.rsp'):
              # Pretend to build so that the rsp files are generated
              self.build(extra_args=['-d', 'keeprsp', '-n'])
              for each in contents:
                  # Extract the actual command from the rsp file
                  compiler, rsp = each['command'].split(' @')
                  rsp = os.path.join(self.builddir, rsp)
                  # Replace the command with its contents
                  with open(rsp, 'r', encoding='utf-8') as f:
                      each['command'] = compiler + ' ' + f.read()
          return contents
  
      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, args):
          if isinstance(args, str):
              args = [args]
          out = subprocess.check_output(self.mintro_command + args + [self.builddir],
                                        universal_newlines=True)
          return json.loads(out)
  
      def assertPathEqual(self, path1, path2):
          '''
          Handles a lot of platform-specific quirks related to paths such as
          separator, case-sensitivity, etc.
          '''
          self.assertEqual(PurePath(path1), PurePath(path2))
  
      def assertPathBasenameEqual(self, path, basename):
          msg = '{!r} does not end with {!r}'.format(path, basename)
          # We cannot use os.path.basename because it returns '' when the path
          # ends with '/' for some silly reason. This is not how the UNIX utility
          # `basename` works.
          path_basename = PurePath(path).parts[-1]
          self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
  
      def assertBuildIsNoop(self):
          ret = self.build()
          if self.backend is Backend.ninja:
              self.assertEqual(ret.split('\n')[-2], self.no_rebuild_stdout)
          elif self.backend is Backend.vs:
              # Ensure that some target said that no rebuild was done
              self.assertIn('CustomBuild:\n  All outputs are up-to-date.', ret)
              self.assertIn('ClCompile:\n  All outputs are up-to-date.', ret)
              self.assertIn('Link:\n  All outputs are up-to-date.', ret)
              # Ensure that no targets were built
              clre = re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)
              linkre = re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)
              self.assertNotRegex(ret, clre)
              self.assertNotRegex(ret, linkre)
          elif self.backend is Backend.xcode:
              raise unittest.SkipTest('Please help us fix this test on the xcode backend')
          else:
              raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
  
      def assertRebuiltTarget(self, target):
          ret = self.build()
          if self.backend is Backend.ninja:
              self.assertIn('Linking target {}'.format(target), ret)
          elif self.backend is Backend.vs:
              # Ensure that this target was rebuilt
              linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE)
              self.assertRegex(ret, linkre)
          elif self.backend is Backend.xcode:
              raise unittest.SkipTest('Please help us fix this test on the xcode backend')
          else:
              raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
  
      def assertPathExists(self, path):
          m = 'Path {!r} should exist'.format(path)
          self.assertTrue(os.path.exists(path), msg=m)
  
      def assertPathDoesNotExist(self, path):
          m = 'Path {!r} should not exist'.format(path)
          self.assertFalse(os.path.exists(path), msg=m)
  
  
  class AllPlatformTests(BasePlatformTests):
      '''
      Tests that should run on all platforms
      '''
      def test_default_options_prefix(self):
          '''
          Tests that setting a prefix in default_options in project() works.
          Can't be an ordinary test because we pass --prefix to meson there.
          https://github.com/mesonbuild/meson/issues/1349
          '''
          testdir = os.path.join(self.common_test_dir, '91 default options')
          self.init(testdir, default_args=False)
          opts = self.introspect('--buildoptions')
          for opt in opts:
              if opt['name'] == 'prefix':
                  prefix = opt['value']
          self.assertEqual(prefix, '/absoluteprefix')
  
      def test_absolute_prefix_libdir(self):
          '''
          Tests that setting absolute paths for --prefix and --libdir work. Can't
          be an ordinary test because these are set via the command-line.
          https://github.com/mesonbuild/meson/issues/1341
          https://github.com/mesonbuild/meson/issues/1345
          '''
          testdir = os.path.join(self.common_test_dir, '91 default options')
          prefix = '/someabs'
          libdir = 'libdir'
          extra_args = ['--prefix=' + prefix,
                        # This can just be a relative path, but we want to test
                        # that passing this as an absolute path also works
                        '--libdir=' + prefix + '/' + libdir]
          self.init(testdir, extra_args, default_args=False)
          opts = self.introspect('--buildoptions')
          for opt in opts:
              if opt['name'] == 'prefix':
                  self.assertEqual(prefix, opt['value'])
              elif opt['name'] == 'libdir':
                  self.assertEqual(libdir, opt['value'])
  
      def test_libdir_must_be_inside_prefix(self):
          '''
          Tests that libdir is forced to be inside prefix no matter how it is set.
          Must be a unit test for obvious reasons.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          # libdir being inside prefix is ok
          args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
          self.init(testdir, args)
          self.wipe()
          # libdir not being inside prefix is not ok
          args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
          self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
          self.wipe()
          # libdir must be inside prefix even when set via mesonconf
          self.init(testdir)
          self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False)
  
      def test_prefix_dependent_defaults(self):
          '''
          Tests that configured directory paths are set to prefix dependent
          defaults.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          expected = {
              '/opt': {'prefix': '/opt',
                       'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
                       'infodir': 'share/info',
                       'libexecdir': 'libexec', 'localedir': 'share/locale',
                       'localstatedir': 'var', 'mandir': 'share/man',
                       'sbindir': 'sbin', 'sharedstatedir': 'com',
                       'sysconfdir': 'etc'},
              '/usr': {'prefix': '/usr',
                       'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
                       'infodir': 'share/info',
                       'libexecdir': 'libexec', 'localedir': 'share/locale',
                       'localstatedir': '/var', 'mandir': 'share/man',
                       'sbindir': 'sbin', 'sharedstatedir': '/var/lib',
                       'sysconfdir': '/etc'},
              '/usr/local': {'prefix': '/usr/local',
                             'bindir': 'bin', 'datadir': 'share',
                             'includedir': 'include', 'infodir': 'share/info',
                             'libexecdir': 'libexec',
                             'localedir': 'share/locale',
                             'localstatedir': '/var/local', 'mandir': 'share/man',
                             'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib',
                             'sysconfdir': 'etc'},
              # N.B. We don't check 'libdir' as it's platform dependent, see
              # default_libdir():
          }
          for prefix in expected:
              args = ['--prefix', prefix]
              self.init(testdir, args, default_args=False)
              opts = self.introspect('--buildoptions')
              for opt in opts:
                  name = opt['name']
                  value = opt['value']
                  if name in expected[prefix]:
                      self.assertEqual(value, expected[prefix][name])
              self.wipe()
  
      def test_default_options_prefix_dependent_defaults(self):
          '''
          Tests that setting a prefix in default_options in project() sets prefix
          dependent defaults for other options, and that those defaults can
          be overridden in default_options or by the command line.
          '''
          testdir = os.path.join(self.common_test_dir, '169 default options prefix dependent defaults')
          expected = {
              '':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/sharedstate'},
              '--prefix=/usr':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/sharedstate'},
              '--sharedstatedir=/var/state':
              {'prefix':         '/usr',
               'sysconfdir':     '/etc',
               'localstatedir':  '/var',
               'sharedstatedir': '/var/state'},
              '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf':
              {'prefix':         '/usr',
               'sysconfdir':     'sysconf',
               'localstatedir':  '/var',
               'sharedstatedir': '/var/state'},
          }
          for args in expected:
              self.init(testdir, args.split(), default_args=False)
              opts = self.introspect('--buildoptions')
              for opt in opts:
                  name = opt['name']
                  value = opt['value']
                  if name in expected[args]:
                      self.assertEqual(value, expected[args][name])
              self.wipe()
  
      def test_static_library_overwrite(self):
          '''
          Tests that static libraries are never appended to, always overwritten.
          Has to be a unit test because this involves building a project,
          reconfiguring, and building it again so that `ar` is run twice on the
          same static library.
          https://github.com/mesonbuild/meson/issues/1355
          '''
          testdir = os.path.join(self.common_test_dir, '3 static')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          static_linker = env.detect_static_linker(cc)
          if is_windows():
              raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526')
          if not isinstance(static_linker, mesonbuild.linkers.ArLinker):
              raise unittest.SkipTest('static linker is not `ar`')
          # Configure
          self.init(testdir)
          # Get name of static library
          targets = self.introspect('--targets')
          self.assertEqual(len(targets), 1)
          libname = targets[0]['filename']
          # Build and get contents of static library
          self.build()
          before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
          # Filter out non-object-file contents
          before = [f for f in before if f.endswith(('.o', '.obj'))]
          # Static library should contain only one object
          self.assertEqual(len(before), 1, msg=before)
          # Change the source to be built into the static library
          self.setconf('-Dsource=libfile2.c')
          self.build()
          after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
          # Filter out non-object-file contents
          after = [f for f in after if f.endswith(('.o', '.obj'))]
          # Static library should contain only one object
          self.assertEqual(len(after), 1, msg=after)
          # and the object must have changed
          self.assertNotEqual(before, after)
  
      def test_static_compile_order(self):
          '''
          Test that the order of files in a compiler command-line while compiling
          and linking statically is deterministic. This can't be an ordinary test
          case because we need to inspect the compiler database.
          https://github.com/mesonbuild/meson/pull/951
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir)
          compdb = self.get_compdb()
          # Rules will get written out in this order
          self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
          self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
          self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
          self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
          # FIXME: We don't have access to the linker command
  
      def test_run_target_files_path(self):
          '''
          Test that run_targets are run from the correct directory
          https://github.com/mesonbuild/meson/issues/957
          '''
          testdir = os.path.join(self.common_test_dir, '55 run target')
          self.init(testdir)
          self.run_target('check_exists')
  
      def test_install_introspection(self):
          '''
          Tests that the Meson introspection API exposes install filenames correctly
          https://github.com/mesonbuild/meson/issues/829
          '''
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          intro = self.introspect('--targets')
          if intro[0]['type'] == 'executable':
              intro = intro[::-1]
          self.assertPathEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
          self.assertPathEqual(intro[1]['install_filename'], '/usr/bin/prog' + exe_suffix)
  
      def test_uninstall(self):
          exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
          testdir = os.path.join(self.common_test_dir, '8 install')
          self.init(testdir)
          self.assertPathDoesNotExist(exename)
          self.install()
          self.assertPathExists(exename)
          self.uninstall()
          self.assertPathDoesNotExist(exename)
  
      def test_forcefallback(self):
          testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
          self.init(testdir, ['--wrap-mode=forcefallback'])
          self.build()
          self.run_tests()
  
      def test_testsetups(self):
          if not shutil.which('valgrind'):
                  raise unittest.SkipTest('Valgrind not installed.')
          testdir = os.path.join(self.unit_test_dir, '2 testsetups')
          self.init(testdir)
          self.build()
          # Run tests without setup
          self.run_tests()
          with open(os.path.join(self.logdir, 'testlog.txt')) as f:
              basic_log = f.read()
          # Run buggy test with setup that has env that will make it fail
          self.assertRaises(subprocess.CalledProcessError,
                            self._run, self.mtest_command + ['--setup=valgrind'])
          with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
              vg_log = f.read()
          self.assertFalse('TEST_ENV is set' in basic_log)
          self.assertFalse('Memcheck' in basic_log)
          self.assertTrue('TEST_ENV is set' in vg_log)
          self.assertTrue('Memcheck' in vg_log)
          # Run buggy test with setup without env that will pass
          self._run(self.mtest_command + ['--setup=wrapper'])
          # Setup with no properties works
          self._run(self.mtest_command + ['--setup=empty'])
          # Setup with only env works
          self._run(self.mtest_command + ['--setup=onlyenv'])
          self._run(self.mtest_command + ['--setup=onlyenv2'])
          self._run(self.mtest_command + ['--setup=onlyenv3'])
          # Setup with only a timeout works
          self._run(self.mtest_command + ['--setup=timeout'])
  
      def test_testsetup_selection(self):
          testdir = os.path.join(self.unit_test_dir, '14 testsetup selection')
          self.init(testdir)
          self.build()
  
          # Run tests without setup
          self.run_tests()
  
          self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo'])
          self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:'])
  
          self._run(self.mtest_command + ['--setup=worksforall'])
          self._run(self.mtest_command + ['--setup=main:worksforall'])
  
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:'])
          self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:'])
          self._run(self.mtest_command + ['--setup=bar:onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=foo:onlyinbar'])
          self.assertRaises(subprocess.CalledProcessError, self._run,
                            self.mtest_command + ['--setup=main:onlyinbar'])
  
      def assertFailedTestCount(self, failure_count, command):
          try:
              self._run(command)
              self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
          except subprocess.CalledProcessError as e:
              self.assertEqual(e.returncode, failure_count)
  
      def test_suite_selection(self):
          testdir = os.path.join(self.unit_test_dir, '4 suite selection')
          self.init(testdir)
          self.build()
  
          self.assertFailedTestCount(3, self.mtest_command)
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
          self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
  
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
          self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
  
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
          self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
          self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
          self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
  
          self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
  
      def test_build_by_default(self):
          testdir = os.path.join(self.common_test_dir, '134 build by default')
          self.init(testdir)
          self.build()
          genfile1 = os.path.join(self.builddir, 'generated1.dat')
          genfile2 = os.path.join(self.builddir, 'generated2.dat')
          exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix)
          exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix)
          self.assertPathExists(genfile1)
          self.assertPathExists(genfile2)
          self.assertPathDoesNotExist(exe1)
          self.assertPathDoesNotExist(exe2)
          self.build(target=('fooprog' + exe_suffix))
          self.assertPathExists(exe1)
          self.build(target=('barprog' + exe_suffix))
          self.assertPathExists(exe2)
  
      def test_internal_include_order(self):
          testdir = os.path.join(self.common_test_dir, '135 include order')
          self.init(testdir)
          execmd = fxecmd = None
          for cmd in self.get_compdb():
              if 'someexe' in cmd['command']:
                  execmd = cmd['command']
                  continue
              if 'somefxe' in cmd['command']:
                  fxecmd = cmd['command']
                  continue
          if not execmd or not fxecmd:
              raise Exception('Could not find someexe and somfxe commands')
          # Check include order for 'someexe'
          incs = [a for a in shlex.split(execmd) if a.startswith("-I")]
          self.assertEqual(len(incs), 9)
          # target private dir
          self.assertPathEqual(incs[0], "-Isub4/sub4@@someexe@exe")
          # target build subdir
          self.assertPathEqual(incs[1], "-Isub4")
          # target source subdir
          self.assertPathBasenameEqual(incs[2], 'sub4')
          # include paths added via per-target c_args: ['-I'...]
          self.assertPathBasenameEqual(incs[3], 'sub3')
          # target include_directories: build dir
          self.assertPathEqual(incs[4], "-Isub2")
          # target include_directories: source dir
          self.assertPathBasenameEqual(incs[5], 'sub2')
          # target internal dependency include_directories: build dir
          self.assertPathEqual(incs[6], "-Isub1")
          # target internal dependency include_directories: source dir
          self.assertPathBasenameEqual(incs[7], 'sub1')
          # custom target include dir
          self.assertPathEqual(incs[8], '-Ictsub')
          # Check include order for 'somefxe'
          incs = [a for a in shlex.split(fxecmd) if a.startswith('-I')]
          self.assertEqual(len(incs), 9)
          # target private dir
          self.assertPathEqual(incs[0], '-Isomefxe@exe')
          # target build dir
          self.assertPathEqual(incs[1], '-I.')
          # target source dir
          self.assertPathBasenameEqual(incs[2], os.path.basename(testdir))
          # target internal dependency correct include_directories: build dir
          self.assertPathEqual(incs[3], "-Isub4")
          # target internal dependency correct include_directories: source dir
          self.assertPathBasenameEqual(incs[4], 'sub4')
          # target internal dependency dep include_directories: build dir
          self.assertPathEqual(incs[5], "-Isub1")
          # target internal dependency dep include_directories: source dir
          self.assertPathBasenameEqual(incs[6], 'sub1')
          # target internal dependency wrong include_directories: build dir
          self.assertPathEqual(incs[7], "-Isub2")
          # target internal dependency wrong include_directories: source dir
          self.assertPathBasenameEqual(incs[8], 'sub2')
  
      def test_compiler_detection(self):
          '''
          Test that automatic compiler detection and setting from the environment
          both work just fine. This is needed because while running project tests
          and other unit tests, we always read CC/CXX/etc from the environment.
          '''
          gnu = mesonbuild.compilers.GnuCompiler
          clang = mesonbuild.compilers.ClangCompiler
          intel = mesonbuild.compilers.IntelCompiler
          msvc = mesonbuild.compilers.VisualStudioCCompiler
          ar = mesonbuild.linkers.ArLinker
          lib = mesonbuild.linkers.VisualStudioLinker
          langs = [('c', 'CC'), ('cpp', 'CXX')]
          if not is_windows():
              langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
          testdir = os.path.join(self.unit_test_dir, '5 compiler detection')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          for lang, evar in langs:
              # Detect with evar and do sanity checks on that
              if evar in os.environ:
                  ecc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
                  self.assertTrue(ecc.version)
                  elinker = env.detect_static_linker(ecc)
                  # Pop it so we don't use it for the next detection
                  evalue = os.environ.pop(evar)
                  # Very rough/strict heuristics. Would never work for actual
                  # compiler detection, but should be ok for the tests.
                  ebase = os.path.basename(evalue)
                  if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')):
                      self.assertIsInstance(ecc, gnu)
                      self.assertIsInstance(elinker, ar)
                  elif 'clang' in ebase:
                      self.assertIsInstance(ecc, clang)
                      self.assertIsInstance(elinker, ar)
                  elif ebase.startswith('ic'):
                      self.assertIsInstance(ecc, intel)
                      self.assertIsInstance(elinker, ar)
                  elif ebase.startswith('cl'):
                      self.assertIsInstance(ecc, msvc)
                      self.assertIsInstance(elinker, lib)
                  else:
                      raise AssertionError('Unknown compiler {!r}'.format(evalue))
                  # Check that we actually used the evalue correctly as the compiler
                  self.assertEqual(ecc.get_exelist(), shlex.split(evalue))
              # Do auto-detection of compiler based on platform, PATH, etc.
              cc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
              self.assertTrue(cc.version)
              linker = env.detect_static_linker(cc)
              # Check compiler type
              if isinstance(cc, gnu):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_OSX)
                  elif is_windows():
                      self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_MINGW)
                  elif is_cygwin():
                      self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_CYGWIN)
                  else:
                      self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_STANDARD)
              if isinstance(cc, clang):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_OSX)
                  elif is_windows():
                      # Not implemented yet
                      self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_WIN)
                  else:
                      self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_STANDARD)
              if isinstance(cc, intel):
                  self.assertIsInstance(linker, ar)
                  if is_osx():
                      self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_OSX)
                  elif is_windows():
                      self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_WIN)
                  else:
                      self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_STANDARD)
              if isinstance(cc, msvc):
                  self.assertTrue(is_windows())
                  self.assertIsInstance(linker, lib)
                  self.assertEqual(cc.id, 'msvc')
                  self.assertTrue(hasattr(cc, 'is_64'))
                  # If we're in the appveyor CI, we know what the compiler will be
                  if 'arch' in os.environ:
                      if os.environ['arch'] == 'x64':
                          self.assertTrue(cc.is_64)
                      else:
                          self.assertFalse(cc.is_64)
              # Set evar ourselves to a wrapper script that just calls the same
              # exelist + some argument. This is meant to test that setting
              # something like `ccache gcc -pipe` or `distcc ccache gcc` works.
              wrapper = os.path.join(testdir, 'compiler wrapper.py')
              wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG']
              wrappercc_s = ''
              for w in wrappercc:
                  wrappercc_s += shlex.quote(w) + ' '
              os.environ[evar] = wrappercc_s
              wcc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
              # Check static linker too
              wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args()
              wrapperlinker_s = ''
              for w in wrapperlinker:
                  wrapperlinker_s += shlex.quote(w) + ' '
              os.environ['AR'] = wrapperlinker_s
              wlinker = env.detect_static_linker(wcc)
              # Must be the same type since it's a wrapper around the same exelist
              self.assertIs(type(cc), type(wcc))
              self.assertIs(type(linker), type(wlinker))
              # Ensure that the exelist is correct
              self.assertEqual(wcc.get_exelist(), wrappercc)
              self.assertEqual(wlinker.get_exelist(), wrapperlinker)
              # Ensure that the version detection worked correctly
              self.assertEqual(cc.version, wcc.version)
              if hasattr(cc, 'is_64'):
                  self.assertEqual(cc.is_64, wcc.is_64)
  
      def test_always_prefer_c_compiler_for_asm(self):
          testdir = os.path.join(self.common_test_dir, '138 c cpp and asm')
          # Skip if building with MSVC
          env = get_fake_env(testdir, self.builddir, self.prefix)
          if env.detect_c_compiler(False).get_id() == 'msvc':
              raise unittest.SkipTest('MSVC can\'t compile assembly')
          self.init(testdir)
          commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}}
          for cmd in self.get_compdb():
              # Get compiler
              split = shlex.split(cmd['command'])
              if split[0] == 'ccache':
                  compiler = split[1]
              else:
                  compiler = split[0]
              # Classify commands
              if 'Ic-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['c-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['c-asm']['c'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
              elif 'Icpp-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['cpp-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['cpp-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
              elif 'Ic-cpp-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['c-cpp-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['c-cpp-asm']['c'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['c-cpp-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command']))
              elif 'Icpp-c-asm' in cmd['command']:
                  if cmd['file'].endswith('.S'):
                      commands['cpp-c-asm']['asm'] = compiler
                  elif cmd['file'].endswith('.c'):
                      commands['cpp-c-asm']['c'] = compiler
                  elif cmd['file'].endswith('.cpp'):
                      commands['cpp-c-asm']['cpp'] = compiler
                  else:
                      raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command']))
              else:
                  raise AssertionError('Unknown command {!r} found'.format(cmd['command']))
          # Check that .S files are always built with the C compiler
          self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c'])
          self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm'])
          self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c'])
          self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c'])
          self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c'])
          self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp'])
          self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp'])
          self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp'])
          # Check that the c-asm target is always linked with the C linker
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('build c-asm.*: c_LINKER', contents)
          self.assertIsNotNone(m, msg=contents)
  
      def test_preprocessor_checks_CPPFLAGS(self):
          '''
          Test that preprocessor compiler checks read CPPFLAGS but not CFLAGS
          '''
          testdir = os.path.join(self.common_test_dir, '137 get define')
          define = 'MESON_TEST_DEFINE_VALUE'
          # NOTE: this list can't have \n, ' or "
          # \n is never substituted by the GNU pre-processor via a -D define
          # ' and " confuse shlex.split() even when they are escaped
          # % and # confuse the MSVC preprocessor
          # !, ^, *, and < confuse lcc preprocessor
          value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`'
          os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value)
          os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define)
          self.init(testdir, ['-D{}={}'.format(define, value)])
  
      def test_custom_target_exe_data_deterministic(self):
          testdir = os.path.join(self.common_test_dir, '114 custom target capture')
          self.init(testdir)
          meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.wipe()
          self.init(testdir)
          meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
          self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
  
      def test_source_changes_cause_rebuild(self):
          '''
          Test that changes to sources and headers cause rebuilds, but not
          changes to unused files (as determined by the dependency file) in the
          input files list.
          '''
          testdir = os.path.join(self.common_test_dir, '20 header in file list')
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Changing mtime of header.h should rebuild everything
          self.utime(os.path.join(testdir, 'header.h'))
          self.assertRebuiltTarget('prog')
  
      def test_custom_target_changes_cause_rebuild(self):
          '''
          Test that in a custom target, changes to the input files, the
          ExternalProgram, and any File objects on the command-line cause
          a rebuild.
          '''
          testdir = os.path.join(self.common_test_dir, '61 custom header generator')
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Changing mtime of these should rebuild everything
          for f in ('input.def', 'makeheader.py', 'somefile.txt'):
              self.utime(os.path.join(testdir, f))
              self.assertRebuiltTarget('prog')
  
      def test_static_library_lto(self):
          '''
          Test that static libraries can be built with LTO and linked to
          executables. On Linux, this requires the use of gcc-ar.
          https://github.com/mesonbuild/meson/issues/1646
          '''
          testdir = os.path.join(self.common_test_dir, '5 linkstatic')
          self.init(testdir, extra_args='-Db_lto=true')
          self.build()
          self.run_tests()
  
      def test_dist_git(self):
          if not shutil.which('git'):
              raise unittest.SkipTest('Git not found')
  
          try:
              self.dist_impl(_git_init)
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the git files so cleaning up the dir
              # fails sometimes.
              pass
  
      def test_dist_hg(self):
          if not shutil.which('hg'):
              raise unittest.SkipTest('Mercurial not found')
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Dist is only supported with Ninja')
  
          def hg_init(project_dir):
              subprocess.check_call(['hg', 'init'], cwd=project_dir)
              with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w') as f:
                  print('[ui]', file=f)
                  print('username=Author Person <teh_coderz@example.com>', file=f)
              subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir)
              subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir)
  
          try:
              self.dist_impl(hg_init)
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the hg files so cleaning up the dir
              # fails sometimes.
              pass
  
      def test_dist_git_script(self):
          if not shutil.which('git'):
              raise unittest.SkipTest('Git not found')
  
          try:
              with tempfile.TemporaryDirectory() as tmpdir:
                  project_dir = os.path.join(tmpdir, 'a')
                  shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'),
                                  project_dir)
                  _git_init(project_dir)
                  self.init(project_dir)
                  self.build('dist')
          except PermissionError:
              # When run under Windows CI, something (virus scanner?)
              # holds on to the git files so cleaning up the dir
              # fails sometimes.
              pass
  
      def dist_impl(self, vcs_init):
          # Create this on the fly because having rogue .git directories inside
          # the source tree leads to all kinds of trouble.
          with tempfile.TemporaryDirectory() as project_dir:
              with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile:
                  ofile.write('''project('disttest', 'c', version : '1.4.3')
  e = executable('distexe', 'distexe.c')
  test('dist test', e)
  ''')
              with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile:
                  ofile.write('''#include<stdio.h>
  
  int main(int argc, char **argv) {
      printf("I am a distribution test.\\n");
      return 0;
  }
  ''')
              vcs_init(project_dir)
              self.init(project_dir)
              self.build('dist')
              distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz')
              checksumfile = distfile + '.sha256sum'
              self.assertPathExists(distfile)
              self.assertPathExists(checksumfile)
  
      def test_rpath_uses_ORIGIN(self):
          '''
          Test that built targets use $ORIGIN in rpath, which ensures that they
          are relocatable and ensures that builds are reproducible since the
          build directory won't get embedded into the built binaries.
          '''
          if is_windows() or is_cygwin():
              raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
          testdir = os.path.join(self.common_test_dir, '43 library chain')
          self.init(testdir)
          self.build()
          for each in ('prog', 'subdir/liblib1.so', ):
              rpath = get_rpath(os.path.join(self.builddir, each))
              self.assertTrue(rpath)
              if is_dragonflybsd():
                  # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath,
                  # so ignore that.
                  self.assertTrue(rpath.startswith('/usr/lib/gcc'))
                  rpaths = rpath.split(':')[1:]
              else:
                  rpaths = rpath.split(':')
              for path in rpaths:
                  self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path))
          # These two don't link to anything else, so they do not need an rpath entry.
          for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'):
              rpath = get_rpath(os.path.join(self.builddir, each))
              if is_dragonflybsd():
                  # The rpath should be equal to /usr/lib/gccVERSION
                  self.assertTrue(rpath.startswith('/usr/lib/gcc'))
                  self.assertEqual(len(rpath.split(':')), 1)
              else:
                  self.assertTrue(rpath is None)
  
      def test_dash_d_dedup(self):
          testdir = os.path.join(self.unit_test_dir, '9 d dedup')
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          self.assertTrue('-D FOO -D BAR' in cmd or
                          '"-D" "FOO" "-D" "BAR"' in cmd or
                          '/D FOO /D BAR' in cmd or
                          '"/D" "FOO" "/D" "BAR"' in cmd)
  
      def test_all_forbidden_targets_tested(self):
          '''
          Test that all forbidden targets are tested in the '155 reserved targets'
          test. Needs to be a unit test because it accesses Meson internals.
          '''
          testdir = os.path.join(self.common_test_dir, '155 reserved targets')
          targets = mesonbuild.coredata.forbidden_target_names
          # We don't actually define a target with this name
          targets.pop('build.ninja')
          # Remove this to avoid multiple entries with the same name
          # but different case.
          targets.pop('PHONY')
          for i in targets:
              self.assertPathExists(os.path.join(testdir, i))
  
      def detect_prebuild_env(self):
          env = get_fake_env('', self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          stlinker = env.detect_static_linker(cc)
          if mesonbuild.mesonlib.is_windows():
              object_suffix = 'obj'
              shared_suffix = 'dll'
          elif mesonbuild.mesonlib.is_cygwin():
              object_suffix = 'o'
              shared_suffix = 'dll'
          elif mesonbuild.mesonlib.is_osx():
              object_suffix = 'o'
              shared_suffix = 'dylib'
          else:
              object_suffix = 'o'
              shared_suffix = 'so'
          return (cc, stlinker, object_suffix, shared_suffix)
  
      def pbcompile(self, compiler, source, objectfile, extra_args=[]):
          cmd = compiler.get_exelist()
          if compiler.id == 'msvc':
              cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args
          else:
              cmd += ['-c', source, '-o', objectfile] + extra_args
          subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  
      def test_prebuilt_object(self):
          (compiler, _, object_suffix, _) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '15 prebuilt object')
          source = os.path.join(tdir, 'source.c')
          objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix)
          self.pbcompile(compiler, source, objectfile)
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(objectfile)
  
      def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None):
          if extra_args is None:
              extra_args = []
          if compiler.id == 'msvc':
              link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile]
          else:
              link_cmd = ['ar', 'csr', outfile, objectfile]
          link_cmd = linker.get_exelist()
          link_cmd += linker.get_always_args()
          link_cmd += linker.get_std_link_args()
          link_cmd += linker.get_output_args(outfile)
          link_cmd += [objectfile]
          self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
          try:
              subprocess.check_call(link_cmd)
          finally:
              os.unlink(objectfile)
  
      def test_prebuilt_static_lib(self):
          (cc, stlinker, object_suffix, _) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '16 prebuilt static')
          source = os.path.join(tdir, 'libdir/best.c')
          objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix)
          stlibfile = os.path.join(tdir, 'libdir/libbest.a')
          self.build_static_lib(cc, stlinker, source, objectfile, stlibfile)
          # Run the test
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(stlibfile)
  
      def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None):
          if extra_args is None:
              extra_args = []
          if compiler.id == 'msvc':
              link_cmd = ['link', '/NOLOGO', '/DLL', '/DEBUG',
                          '/IMPLIB:' + impfile, '/OUT:' + outfile, objectfile]
          else:
              extra_args += ['-fPIC']
              link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile]
              if not mesonbuild.mesonlib.is_osx():
                  link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)]
          self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
          try:
              subprocess.check_call(link_cmd)
          finally:
              os.unlink(objectfile)
  
      def test_prebuilt_shared_lib(self):
          (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
          tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
          source = os.path.join(tdir, 'alexandria.c')
          objectfile = os.path.join(tdir, 'alexandria.' + object_suffix)
          impfile = os.path.join(tdir, 'alexandria.lib')
          if cc.id == 'msvc':
              shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix)
          elif is_cygwin():
              shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix)
          else:
              shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix)
          self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
          # Run the test
          try:
              self.init(tdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(shlibfile)
              if mesonbuild.mesonlib.is_windows():
                  # Clean up all the garbage MSVC writes in the
                  # source tree.
                  for fname in glob(os.path.join(tdir, 'alexandria.*')):
                      if os.path.splitext(fname)[1] not in ['.c', '.h']:
                          os.unlink(fname)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_static(self):
          '''
          Test that the we prefer static libraries when `static: true` is
          passed to dependency() with pkg-config. Can't be an ordinary test
          because we need to build libs and try to find them from meson.build
  
          Also test that it's not a hard error to have unsatisfiable library deps
          since system libraries -lm will never be found statically.
          https://github.com/mesonbuild/meson/issues/2785
          '''
          (cc, stlinker, objext, shext) = self.detect_prebuild_env()
          testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static')
          source = os.path.join(testdir, 'foo.c')
          objectfile = os.path.join(testdir, 'foo.' + objext)
          stlibfile = os.path.join(testdir, 'libfoo.a')
          impfile = os.path.join(testdir, 'foo.lib')
          if cc.id == 'msvc':
              shlibfile = os.path.join(testdir, 'foo.' + shext)
          elif is_cygwin():
              shlibfile = os.path.join(testdir, 'cygfoo.' + shext)
          else:
              shlibfile = os.path.join(testdir, 'libfoo.' + shext)
          # Build libs
          self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC'])
          self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
          # Run test
          os.environ['PKG_CONFIG_LIBDIR'] = self.builddir
          try:
              self.init(testdir)
              self.build()
              self.run_tests()
          finally:
              os.unlink(stlibfile)
              os.unlink(shlibfile)
              if mesonbuild.mesonlib.is_windows():
                  # Clean up all the garbage MSVC writes in the
                  # source tree.
                  for fname in glob(os.path.join(testdir, 'foo.*')):
                      if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']:
                          os.unlink(fname)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_gen_escaping(self):
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen')
          prefix = '/usr/with spaces'
          libdir = 'lib'
          self.init(testdir, extra_args=['--prefix=' + prefix,
                                         '--libdir=' + libdir])
          # Find foo dependency
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          env = get_fake_env(testdir, self.builddir, self.prefix)
          kwargs = {'required': True, 'silent': True}
          foo_dep = PkgConfigDependency('libfoo', env, kwargs)
          # Ensure link_args are properly quoted
          libdir = PurePath(prefix) / PurePath(libdir)
          link_args = ['-L' + libdir.as_posix(), '-lfoo']
          self.assertEqual(foo_dep.get_link_args(), link_args)
          # Ensure include args are properly quoted
          incdir = PurePath(prefix) / PurePath('include')
          cargs = ['-I' + incdir.as_posix()]
          self.assertEqual(foo_dep.get_compile_args(), cargs)
  
      def test_array_option_change(self):
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': ['foo', 'bar'],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir)
          original = get_opt()
          self.assertDictEqual(original, expected)
  
          expected['value'] = ['oink', 'boink']
          self.setconf('-Dlist=oink,boink')
          changed = get_opt()
          self.assertEqual(changed, expected)
  
      def test_array_option_bad_change(self):
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': ['foo', 'bar'],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir)
          original = get_opt()
          self.assertDictEqual(original, expected)
          with self.assertRaises(subprocess.CalledProcessError):
              self.setconf('-Dlist=bad')
          changed = get_opt()
          self.assertDictEqual(changed, expected)
  
      def test_array_option_empty_equivalents(self):
          """Array options treat -Dopt=[] and -Dopt= as equivalent."""
          def get_opt():
              opts = self.introspect('--buildoptions')
              for x in opts:
                  if x.get('name') == 'list':
                      return x
              raise Exception(opts)
  
          expected = {
              'name': 'list',
              'description': 'list',
              'type': 'array',
              'value': [],
          }
          tdir = os.path.join(self.unit_test_dir, '19 array option')
          self.init(tdir, extra_args='-Dlist=')
          original = get_opt()
          self.assertDictEqual(original, expected)
  
      def opt_has(self, name, value):
          res = self.introspect('--buildoptions')
          found = False
          for i in res:
              if i['name'] == name:
                  self.assertEqual(i['value'], value)
                  found = True
                  break
          self.assertTrue(found, "Array option not found in introspect data.")
  
      def test_free_stringarray_setting(self):
          testdir = os.path.join(self.common_test_dir, '44 options')
          self.init(testdir)
          self.opt_has('free_array_opt', [])
          self.setconf('-Dfree_array_opt=foo,bar', will_build=False)
          self.opt_has('free_array_opt', ['foo', 'bar'])
          self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False)
          self.opt_has('free_array_opt', ['a,b', 'c,d'])
  
      def test_subproject_promotion(self):
          testdir = os.path.join(self.unit_test_dir, '12 promote')
          workdir = os.path.join(self.builddir, 'work')
          shutil.copytree(testdir, workdir)
          spdir = os.path.join(workdir, 'subprojects')
          s3dir = os.path.join(spdir, 's3')
          scommondir = os.path.join(spdir, 'scommon')
          self.assertFalse(os.path.isdir(s3dir))
          subprocess.check_call(self.wrap_command + ['promote', 's3'], cwd=workdir)
          self.assertTrue(os.path.isdir(s3dir))
          self.assertFalse(os.path.isdir(scommondir))
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'],
                                              cwd=workdir,
                                              stdout=subprocess.DEVNULL), 0)
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'],
                                              cwd=workdir,
                                              stderr=subprocess.DEVNULL), 0)
          self.assertFalse(os.path.isdir(scommondir))
          subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir)
          self.assertTrue(os.path.isdir(scommondir))
          promoted_wrap = os.path.join(spdir, 'athing.wrap')
          self.assertFalse(os.path.isfile(promoted_wrap))
          subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir)
          self.assertTrue(os.path.isfile(promoted_wrap))
          self.init(workdir)
          self.build()
  
      def test_subproject_promotion_wrap(self):
          testdir = os.path.join(self.unit_test_dir, '44 promote wrap')
          workdir = os.path.join(self.builddir, 'work')
          shutil.copytree(testdir, workdir)
          spdir = os.path.join(workdir, 'subprojects')
  
          ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap')
          self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'],
                                              cwd=workdir,
                                              stdout=subprocess.DEVNULL), 0)
          self.assertFalse(os.path.isfile(ambiguous_wrap))
          subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir)
          self.assertTrue(os.path.isfile(ambiguous_wrap))
  
      def test_warning_location(self):
          tdir = os.path.join(self.unit_test_dir, '22 warning location')
          out = self.init(tdir)
          for expected in [
              r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.',
              r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.',
              r'meson.build:6: WARNING: a warning of some sort',
              r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning',
              r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.',
              r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.",
              r'meson.build:1: WARNING: Passed invalid keyword argument "invalid".',
          ]:
              self.assertRegex(out, re.escape(expected))
  
      def test_permitted_method_kwargs(self):
          tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs')
          out = self.init(tdir)
          for expected in [
              r'WARNING: Passed invalid keyword argument "prefixxx".',
              r'WARNING: Passed invalid keyword argument "argsxx".',
              r'WARNING: Passed invalid keyword argument "invalidxx".',
          ]:
              self.assertRegex(out, re.escape(expected))
  
      def test_templates(self):
          ninja = detect_ninja()
          if ninja is None:
              raise unittest.SkipTest('This test currently requires ninja. Fix this once "meson build" works.')
          for lang in ('c', 'cpp'):
              for type in ('executable', 'library'):
                  with tempfile.TemporaryDirectory() as tmpdir:
                      self._run(self.meson_command + ['init', '--language', lang, '--type', type],
                                workdir=tmpdir)
                      self._run(self.setup_command + ['--backend=ninja', 'builddir'],
                                workdir=tmpdir)
                      self._run(ninja,
                                workdir=os.path.join(tmpdir, 'builddir'))
              with tempfile.TemporaryDirectory() as tmpdir:
                  with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f:
                      f.write('int main() {}')
                  self._run(self.meson_command + ['init', '-b'], workdir=tmpdir)
  
      # The test uses mocking and thus requires that
      # the current process is the one to run the Meson steps.
      # If we are using an external test executable (most commonly
      # in Debian autopkgtests) then the mocking won't work.
      @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.')
      def test_cross_file_system_paths(self):
          if is_windows():
              raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)')
  
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          cross_content = textwrap.dedent("""\
              [binaries]
              c = '/usr/bin/cc'
              ar = '/usr/bin/ar'
              strip = '/usr/bin/ar'
  
              [properties]
  
              [host_machine]
              system = 'linux'
              cpu_family = 'x86'
              cpu = 'i686'
              endian = 'little'
              """)
  
          with tempfile.TemporaryDirectory() as d:
              dir_ = os.path.join(d, 'meson', 'cross')
              os.makedirs(dir_)
              with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
                  f.write(cross_content)
              name = os.path.basename(f.name)
  
              with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}):
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
              with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}):
                  os.environ.pop('XDG_DATA_HOME', None)
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
          with tempfile.TemporaryDirectory() as d:
              dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross')
              os.makedirs(dir_)
              with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
                  f.write(cross_content)
              name = os.path.basename(f.name)
  
              with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)):
                  self.init(testdir, ['--cross-file=' + name], inprocess=True)
                  self.wipe()
  
      def test_compiler_run_command(self):
          '''
          The test checks that the compiler object can be passed to
          run_command().
          '''
          testdir = os.path.join(self.unit_test_dir, '24 compiler run_command')
          self.init(testdir)
  
      def test_identical_target_name_in_subproject_flat_layout(self):
          '''
          Test that identical targets in different subprojects do not collide
          if layout is flat.
          '''
          testdir = os.path.join(self.common_test_dir, '178 identical target name in subproject flat layout')
          self.init(testdir, extra_args=['--layout=flat'])
          self.build()
  
      def test_identical_target_name_in_subdir_flat_layout(self):
          '''
          Test that identical targets in different subdirs do not collide
          if layout is flat.
          '''
          testdir = os.path.join(self.common_test_dir, '187 same target name flat layout')
          self.init(testdir, extra_args=['--layout=flat'])
          self.build()
  
      def test_flock(self):
          exception_raised = False
          with tempfile.TemporaryDirectory() as tdir:
              os.mkdir(os.path.join(tdir, 'meson-private'))
              with BuildDirLock(tdir):
                  try:
                      with BuildDirLock(tdir):
                          pass
                  except MesonException:
                      exception_raised = True
          self.assertTrue(exception_raised, 'Double locking did not raise exception.')
  
      @unittest.skipIf(is_osx(), 'Test not applicable to OSX')
      def test_check_module_linking(self):
          """
          Test that link_with: a shared module issues a warning
          https://github.com/mesonbuild/meson/issues/2865
          (That an error is raised on OSX is exercised by test failing/78)
          """
          tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking')
          out = self.init(tdir)
          msg = ('''WARNING: target links against shared modules. This is not
  recommended as it is not supported on some platforms''')
          self.assertIn(msg, out)
  
      def test_ndebug_if_release_disabled(self):
          testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
          self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release'])
          self.build()
          exe = os.path.join(self.builddir, 'main')
          self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip())
  
      def test_ndebug_if_release_enabled(self):
          testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
          self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release'])
          self.build()
          exe = os.path.join(self.builddir, 'main')
          self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip())
  
      def test_guessed_linker_dependencies(self):
          '''
          Test that meson adds dependencies for libraries based on the final
          linker command line.
          '''
          # build library
          testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies')
          testdirlib = os.path.join(testdirbase, 'lib')
          extra_args = None
          env = get_fake_env(testdirlib, self.builddir, self.prefix)
          if env.detect_c_compiler(False).get_id() != 'msvc':
              # static libraries are not linkable with -l with msvc because meson installs them
              # as .a files which unix_args_to_native will not know as it expects libraries to use
              # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc
              # this tests needs to use shared libraries to test the path resolving logic in the
              # dependency generation code path.
              extra_args = ['--default-library', 'static']
          self.init(testdirlib, extra_args=extra_args)
          self.build()
          self.install()
          libbuilddir = self.builddir
          installdir = self.installdir
          libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib')
  
          # build user of library
          self.new_builddir()
          # replace is needed because meson mangles platform pathes passed via LDFLAGS
          os.environ["LDFLAGS"] = '-L{}'.format(libdir.replace('\\', '/'))
          self.init(os.path.join(testdirbase, 'exe'))
          del os.environ["LDFLAGS"]
          self.build()
          self.assertBuildIsNoop()
  
          # rebuild library
          exebuilddir = self.builddir
          self.installdir = installdir
          self.builddir = libbuilddir
          # Microsoft's compiler is quite smart about touching import libs on changes,
          # so ensure that there is actually a change in symbols.
          self.setconf('-Dmore_exports=true')
          self.build()
          self.install()
          # no ensure_backend_detects_changes needed because self.setconf did that already
  
          # assert user of library will be rebuild
          self.builddir = exebuilddir
          self.assertRebuiltTarget('app')
  
      def test_conflicting_d_dash_option(self):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          with self.assertRaises(subprocess.CalledProcessError) as e:
              self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar'])
              # Just to ensure that we caught the correct error
              self.assertIn('passed as both', e.stderr)
  
      def _test_same_option_twice(self, arg, args):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          self.init(testdir, extra_args=args)
          opts = self.introspect('--buildoptions')
          for item in opts:
              if item['name'] == arg:
                  self.assertEqual(item['value'], 'bar')
                  return
          raise Exception('Missing {} value?'.format(arg))
  
      def test_same_dash_option_twice(self):
          self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar'])
  
      def test_same_d_option_twice(self):
          self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar'])
  
      def test_same_project_d_option_twice(self):
          self._test_same_option_twice('one', ['-Done=foo', '-Done=bar'])
  
      def _test_same_option_twice_configure(self, arg, args):
          testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
          self.init(testdir)
          self.setconf(args)
          opts = self.introspect('--buildoptions')
          for item in opts:
              if item['name'] == arg:
                  self.assertEqual(item['value'], 'bar')
                  return
          raise Exception('Missing {} value?'.format(arg))
  
      def test_same_dash_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'bindir', ['--bindir=foo', '--bindir=bar'])
  
      def test_same_d_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'bindir', ['-Dbindir=foo', '-Dbindir=bar'])
  
      def test_same_project_d_option_twice_configure(self):
          self._test_same_option_twice_configure(
              'one', ['-Done=foo', '-Done=bar'])
  
      def test_command_line(self):
          testdir = os.path.join(self.unit_test_dir, '34 command line')
  
          # Verify default values when passing no args
          self.init(testdir)
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'static')
          self.assertEqual(obj.builtins['warning_level'].value, '1')
          self.assertEqual(obj.user_options['set_sub_opt'].value, True)
          self.assertEqual(obj.user_options['subp:subp_opt'].value, 'default3')
          self.wipe()
  
          # warning_level is special, it's --warnlevel instead of --warning-level
          # for historical reasons
          self.init(testdir, extra_args=['--warnlevel=2'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '2')
          self.setconf('--warnlevel=3')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '3')
          self.wipe()
  
          # But when using -D syntax, it should be 'warning_level'
          self.init(testdir, extra_args=['-Dwarning_level=2'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '2')
          self.setconf('-Dwarning_level=3')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['warning_level'].value, '3')
          self.wipe()
  
          # Mixing --option and -Doption is forbidden
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('as both', cm.exception.output)
          self.init(testdir)
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.setconf(['--warnlevel=1', '-Dwarning_level=3'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('as both', cm.exception.output)
          self.wipe()
  
          # --default-library should override default value from project()
          self.init(testdir, extra_args=['--default-library=both'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'both')
          self.setconf('--default-library=shared')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.builtins['default_library'].value, 'shared')
          if self.backend is Backend.ninja:
              # reconfigure target works only with ninja backend
              self.build('reconfigure')
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['default_library'].value, 'shared')
          self.wipe()
  
          # Should warn on unknown options
          out = self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo'])
          self.assertIn('Unknown options: "bad, foo, wrong_link_args"', out)
          self.wipe()
  
          # Should fail on malformed option
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(testdir, extra_args=['-Dfoo'])
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
          self.init(testdir)
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.setconf('-Dfoo')
          self.assertNotEqual(0, cm.exception.returncode)
          self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
          self.wipe()
  
          # It is not an error to set wrong option for unknown subprojects or
          # language because we don't have control on which one will be selected.
          self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1'])
          self.wipe()
  
          # Test we can set subproject option
          self.init(testdir, extra_args=['-Dsubp:subp_opt=foo'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.user_options['subp:subp_opt'].value, 'foo')
          self.wipe()
  
          # c_args value should be parsed with shlex
          self.init(testdir, extra_args=['-Dc_args=foo bar "one two"'])
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.compiler_options['c_args'].value, ['foo', 'bar', 'one two'])
          self.setconf('-Dc_args="foo bar" one two')
          obj = mesonbuild.coredata.load(self.builddir)
          self.assertEqual(obj.compiler_options['c_args'].value, ['foo bar', 'one', 'two'])
          self.wipe()
  
          # Setting a 2nd time the same option should override the first value
          try:
              self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar',
                                             '-Dbuildtype=plain', '-Dbuildtype=release',
                                             '-Db_sanitize=address', '-Db_sanitize=thread',
                                             '-Dc_args=foo', '-Dc_args=bar'])
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['bindir'].value, 'bar')
              self.assertEqual(obj.builtins['buildtype'].value, 'release')
              self.assertEqual(obj.base_options['b_sanitize'].value, 'thread')
              self.assertEqual(obj.compiler_options['c_args'].value, ['bar'])
              self.setconf(['--bindir=bar', '--bindir=foo',
                            '-Dbuildtype=release', '-Dbuildtype=plain',
                            '-Db_sanitize=thread', '-Db_sanitize=address',
                            '-Dc_args=bar', '-Dc_args=foo'])
              obj = mesonbuild.coredata.load(self.builddir)
              self.assertEqual(obj.builtins['bindir'].value, 'foo')
              self.assertEqual(obj.builtins['buildtype'].value, 'plain')
              self.assertEqual(obj.base_options['b_sanitize'].value, 'address')
              self.assertEqual(obj.compiler_options['c_args'].value, ['foo'])
              self.wipe()
          except KeyError:
              # Ignore KeyError, it happens on CI for compilers that does not
              # support b_sanitize. We have to test with a base option because
              # they used to fail this test with Meson 0.46 an earlier versions.
              pass
  
      def test_feature_check_usage_subprojects(self):
          testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects')
          out = self.init(testdir)
          # Parent project warns correctly
          self.assertRegex(out, "WARNING: Project targetting '>=0.45'.*'0.47.0': dict")
          # Subprojects warn correctly
          self.assertRegex(out, r"\|WARNING: Project targetting '>=0.40'.*'0.44.0': disabler")
          self.assertRegex(out, r"\|WARNING: Project targetting '!=0.40'.*'0.44.0': disabler")
          # Subproject has a new-enough meson_version, no warning
          self.assertNotRegex(out, "WARNING: Project targetting.*Python")
          # Ensure a summary is printed in the subproject and the outer project
          self.assertRegex(out, r"\|WARNING: Project specifies a minimum meson_version '>=0.40'")
          self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}")
          self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'")
          self.assertRegex(out, " * 0.47.0: {'dict'}")
  
      def test_configure_file_warnings(self):
          testdir = os.path.join(self.common_test_dir, "14 configure file")
          out = self.init(testdir)
          self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
          self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*")
          self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
          self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in")
          # Warnings for configuration files that are overwritten.
          self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites")
          self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites")
          self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites")
          # No warnings about empty configuration data objects passed to files with substitutions
          self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in")
          self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in")
          with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f:
              self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */')
          with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f:
              self.assertEqual(f.read().strip(), b'')
          self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict")
  
      def test_dirs(self):
          with tempfile.TemporaryDirectory() as containing:
              with tempfile.TemporaryDirectory(dir=containing) as srcdir:
                  mfile = os.path.join(srcdir, 'meson.build')
                  of = open(mfile, 'w')
                  of.write("project('foobar', 'c')\n")
                  of.close()
                  pc = subprocess.run(self.setup_command,
                                      cwd=srcdir,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.DEVNULL)
                  self.assertIn(b'Must specify at least one directory name', pc.stdout)
                  with tempfile.TemporaryDirectory(dir=srcdir) as builddir:
                      subprocess.run(self.setup_command,
                                     check=True,
                                     cwd=builddir,
                                     stdout=subprocess.DEVNULL,
                                     stderr=subprocess.DEVNULL)
  
      def get_opts_as_dict(self):
          result = {}
          for i in self.introspect('--buildoptions'):
              result[i['name']] = i['value']
          return result
  
      def test_buildtype_setting(self):
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          self.init(testdir)
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'debug')
          self.assertEqual(opts['debug'], True)
          self.setconf('-Ddebug=false')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['debug'], False)
          self.assertEqual(opts['buildtype'], 'plain')
          self.assertEqual(opts['optimization'], '0')
  
          # Setting optimizations to 3 should cause buildtype
          # to go to release mode.
          self.setconf('-Doptimization=3')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'release')
          self.assertEqual(opts['debug'], False)
          self.assertEqual(opts['optimization'], '3')
  
          # Going to debug build type should reset debugging
          # and optimization
          self.setconf('-Dbuildtype=debug')
          opts = self.get_opts_as_dict()
          self.assertEqual(opts['buildtype'], 'debug')
          self.assertEqual(opts['debug'], True)
          self.assertEqual(opts['optimization'], '0')
  
  
  class FailureTests(BasePlatformTests):
      '''
      Tests that test failure conditions. Build files here should be dynamically
      generated and static tests should go into `test cases/failing*`.
      This is useful because there can be many ways in which a particular
      function can fail, and creating failing tests for all of them is tedious
      and slows down testing.
      '''
      dnf = "[Dd]ependency.*not found"
      nopkg = '[Pp]kg-config not found'
  
      def setUp(self):
          super().setUp()
          self.srcdir = os.path.realpath(tempfile.mkdtemp())
          self.mbuild = os.path.join(self.srcdir, 'meson.build')
  
      def tearDown(self):
          super().tearDown()
          windows_proof_rmtree(self.srcdir)
  
      def assertMesonRaises(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents raises
          a error message matching regex @match.
          '''
          if langs is None:
              langs = []
          with open(self.mbuild, 'w') as f:
              f.write("project('failure test', 'c', 'cpp'")
              if meson_version:
                  f.write(", meson_version: '{}'".format(meson_version))
              f.write(")\n")
              for lang in langs:
                  f.write("add_languages('{}', required : false)\n".format(lang))
              f.write(contents)
          # Force tracebacks so we can detect them properly
          os.environ['MESON_FORCE_BACKTRACE'] = '1'
          with self.assertRaisesRegex(MesonException, match, msg=contents):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(self.srcdir, extra_args=extra_args, inprocess=True)
  
      def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None):
          if langs is None:
              langs = []
          with open(self.mbuild, 'w') as f:
              f.write("project('output test', 'c', 'cpp'")
              if meson_version:
                  f.write(", meson_version: '{}'".format(meson_version))
              f.write(")\n")
              for lang in langs:
                  f.write("add_languages('{}', required : false)\n".format(lang))
              f.write(contents)
          # Run in-process for speed and consistency with assertMesonRaises
          return self.init(self.srcdir, extra_args=extra_args, inprocess=True)
  
      def assertMesonOutputs(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents outputs
          something that matches regex @match.
          '''
          out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
          self.assertRegex(out, match)
  
      def assertMesonDoesNotOutput(self, contents, match, extra_args=None, langs=None, meson_version=None):
          '''
          Assert that running meson configure on the specified @contents does not output
          something that matches regex @match.
          '''
          out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
          self.assertNotRegex(out, match)
  
      @skipIfNoPkgconfig
      def test_dependency(self):
          if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0:
              raise unittest.SkipTest('zlib not found with pkg-config')
          a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"),
               ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"),
               ("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"),
               ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"),
               ("dependency('zlib', method : 1)", "[Mm]ethod.*string"),
               ("dependency('zlibfail')", self.dnf),)
          for contents, match in a:
              self.assertMesonRaises(contents, match)
  
      def test_apple_frameworks_dependency(self):
          if not is_osx():
              raise unittest.SkipTest('only run on macOS')
          self.assertMesonRaises("dependency('appleframeworks')",
                                 "requires at least one module")
  
      def test_sdl2_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('sdl2-config'):
              raise unittest.SkipTest('sdl2-config found')
          self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf)
          if shutil.which('pkg-config'):
              errmsg = self.dnf
          else:
              errmsg = self.nopkg
          self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", errmsg)
  
      def test_gnustep_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('gnustep-config'):
              raise unittest.SkipTest('gnustep-config found')
          self.assertMesonRaises("dependency('gnustep')",
                                 "(requires a Objc compiler|{})".format(self.dnf),
                                 langs = ['objc'])
  
      def test_wx_notfound_dependency(self):
          # Want to test failure, so skip if available
          if shutil.which('wx-config-3.0') or shutil.which('wx-config'):
              raise unittest.SkipTest('wx-config or wx-config-3.0 found')
          self.assertMesonRaises("dependency('wxwidgets')", self.dnf)
          self.assertMesonOutputs("dependency('wxwidgets', required : false)",
                                  "Dependency .*WxWidgets.* found: .*NO.*")
  
      def test_wx_dependency(self):
          if not shutil.which('wx-config-3.0') and not shutil.which('wx-config'):
              raise unittest.SkipTest('Neither wx-config nor wx-config-3.0 found')
          self.assertMesonRaises("dependency('wxwidgets', modules : 1)",
                                 "module argument is not a string")
  
      def test_llvm_dependency(self):
          self.assertMesonRaises("dependency('llvm', modules : 'fail')",
                                 "(required.*fail|{})".format(self.dnf))
  
      def test_boost_notfound_dependency(self):
          # Can be run even if Boost is found or not
          self.assertMesonRaises("dependency('boost', modules : 1)",
                                 "module.*not a string")
          self.assertMesonRaises("dependency('boost', modules : 'fail')",
                                 "(fail.*not found|{})".format(self.dnf))
  
      def test_boost_BOOST_ROOT_dependency(self):
          # Test BOOST_ROOT; can be run even if Boost is found or not
          os.environ['BOOST_ROOT'] = 'relative/path'
          self.assertMesonRaises("dependency('boost')",
                                 "(BOOST_ROOT.*absolute|{})".format(self.dnf))
  
      def test_dependency_invalid_method(self):
          code = '''zlib_dep = dependency('zlib', required : false)
          zlib_dep.get_configtool_variable('foo')
          '''
          self.assertMesonRaises(code, "'zlib' is not a config-tool dependency")
          code = '''zlib_dep = dependency('zlib', required : false)
          dep = declare_dependency(dependencies : zlib_dep)
          dep.get_pkgconfig_variable('foo')
          '''
          self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal")
          code = '''zlib_dep = dependency('zlib', required : false)
          dep = declare_dependency(dependencies : zlib_dep)
          dep.get_configtool_variable('foo')
          '''
          self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal")
  
      def test_objc_cpp_detection(self):
          '''
          Test that when we can't detect objc or objcpp, we fail gracefully.
          '''
          env = get_fake_env('', self.builddir, self.prefix)
          try:
              env.detect_objc_compiler(False)
              env.detect_objcpp_compiler(False)
          except EnvironmentException:
              code = "add_languages('objc')\nadd_languages('objcpp')"
              self.assertMesonRaises(code, "Unknown compiler")
              return
          raise unittest.SkipTest("objc and objcpp found, can't test detection failure")
  
      def test_subproject_variables(self):
          '''
          Test that:
          1. The correct message is outputted when a not-required dep is not
             found and the fallback subproject is also not found.
          2. A not-found not-required dep with a fallback subproject outputs the
             correct message when the fallback subproject is found but the
             variable inside it is not.
          3. A fallback dependency is found from the subproject parsed in (2)
          4. A not-required fallback dependency is not found because the
             subproject failed to parse.
          '''
          tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables')
          out = self.init(tdir, inprocess=True)
          self.assertRegex(out, r"Couldn't use fallback subproject "
                           "in.*subprojects.*nosubproj.*for the dependency.*somedep")
          self.assertRegex(out, r'Dependency.*somenotfounddep.*from subproject.*'
                           'subprojects.*somesubproj.*found:.*NO')
          self.assertRegex(out, r'Dependency.*zlibproxy.*from subproject.*'
                           'subprojects.*somesubproj.*found:.*YES.*(cached)')
          self.assertRegex(out, r'Couldn\'t use fallback subproject in '
                           '.*subprojects.*failingsubproj.*for the dependency.*somedep')
  
      def test_exception_exit_status(self):
          '''
          Test exit status on python exception
          '''
          tdir = os.path.join(self.unit_test_dir, '21 exit status')
          os.environ['MESON_UNIT_TEST'] = '1'
          with self.assertRaises(subprocess.CalledProcessError) as cm:
              self.init(tdir, inprocess=False)
          self.assertEqual(cm.exception.returncode, 2)
          self.wipe()
  
      def test_dict_requires_key_value_pairs(self):
          self.assertMesonRaises("dict = {3, 'foo': 'bar'}",
                                 'Only key:value pairs are valid in dict construction.')
          self.assertMesonRaises("{'foo': 'bar', 3}",
                                 'Only key:value pairs are valid in dict construction.')
  
      def test_dict_forbids_duplicate_keys(self):
          self.assertMesonRaises("dict = {'a': 41, 'a': 42}",
                                 'Duplicate dictionary key: a.*')
  
      def test_dict_forbids_integer_key(self):
          self.assertMesonRaises("dict = {3: 'foo'}",
                                 'Key must be a string.*')
  
      def test_using_too_recent_feature(self):
          # Here we use a dict, which was introduced in 0.47.0
          self.assertMesonOutputs("dict = {}",
                                  ".*WARNING.*Project targetting.*but.*",
                                  meson_version='>= 0.46.0')
  
      def test_using_recent_feature(self):
          # Same as above, except the meson version is now appropriate
          self.assertMesonDoesNotOutput("dict = {}",
                                        ".*WARNING.*Project targetting.*but.*",
                                        meson_version='>= 0.47')
  
      def test_using_too_recent_feature_dependency(self):
          self.assertMesonOutputs("dependency('pcap', required: false)",
                                  ".*WARNING.*Project targetting.*but.*",
                                  meson_version='>= 0.41.0')
  
      def test_vcs_tag_featurenew_build_always_stale(self):
          'https://github.com/mesonbuild/meson/issues/3904'
          vcs_tag = '''version_data = configuration_data()
          version_data.set('PROJVER', '@VCS_TAG@')
          vf = configure_file(output : 'version.h.in', configuration: version_data)
          f = vcs_tag(input : vf, output : 'version.h')
          '''
          msg = '.*WARNING:.*feature.*build_always_stale.*custom_target.*'
          self.assertMesonDoesNotOutput(vcs_tag, msg, meson_version='>=0.43')
  
      def test_missing_subproject_not_required_and_required(self):
          self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
                                 "sub2 = subproject('not-found-subproject', required: true)",
                                 """.*Subproject "subprojects/not-found-subproject" required but not found.*""")
  
      def test_get_variable_on_not_found_project(self):
          self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
                                 "sub1.get_variable('naaa')",
                                 """Subproject "subprojects/not-found-subproject" disabled can't get_variable on it.""")
  
  
  class WindowsTests(BasePlatformTests):
      '''
      Tests that should run on Cygwin, MinGW, and MSVC
      '''
      def setUp(self):
          super().setUp()
          self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows')
  
      @unittest.skipIf(is_cygwin(), 'Test only applicable to Windows')
      def test_find_program(self):
          '''
          Test that Windows-specific edge-cases in find_program are functioning
          correctly. Cannot be an ordinary test because it involves manipulating
          PATH to point to a directory with Python scripts.
          '''
          testdir = os.path.join(self.platform_test_dir, '8 find program')
          # Find `cmd` and `cmd.exe`
          prog1 = ExternalProgram('cmd')
          self.assertTrue(prog1.found(), msg='cmd not found')
          prog2 = ExternalProgram('cmd.exe')
          self.assertTrue(prog2.found(), msg='cmd.exe not found')
          self.assertPathEqual(prog1.get_path(), prog2.get_path())
          # Find cmd with an absolute path that's missing the extension
          cmd_path = prog2.get_path()[:-4]
          prog = ExternalProgram(cmd_path)
          self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path))
          # Finding a script with no extension inside a directory works
          prog = ExternalProgram(os.path.join(testdir, 'test-script'))
          self.assertTrue(prog.found(), msg='test-script not found')
          # Finding a script with an extension inside a directory works
          prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py'))
          self.assertTrue(prog.found(), msg='test-script-ext.py not found')
          # Finding a script in PATH w/o extension works and adds the interpreter
          os.environ['PATH'] += os.pathsep + testdir
          prog = ExternalProgram('test-script-ext')
          self.assertTrue(prog.found(), msg='test-script-ext not found in PATH')
          self.assertPathEqual(prog.get_command()[0], python_command[0])
          self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
          # Finding a script in PATH with extension works and adds the interpreter
          prog = ExternalProgram('test-script-ext.py')
          self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH')
          self.assertPathEqual(prog.get_command()[0], python_command[0])
          self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
  
      def test_ignore_libs(self):
          '''
          Test that find_library on libs that are to be ignored returns an empty
          array of arguments. Must be a unit test because we cannot inspect
          ExternalLibraryHolder from build files.
          '''
          testdir = os.path.join(self.platform_test_dir, '1 basic')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          if cc.id != 'msvc':
              raise unittest.SkipTest('Not using MSVC')
          # To force people to update this test, and also test
          self.assertEqual(set(cc.ignore_libs), {'c', 'm', 'pthread', 'dl', 'rt'})
          for l in cc.ignore_libs:
              self.assertEqual(cc.find_library(l, env, []), [])
  
      def test_rc_depends_files(self):
          testdir = os.path.join(self.platform_test_dir, '5 resources')
  
          # resource compiler depfile generation is not yet implemented for msvc
          env = get_fake_env(testdir, self.builddir, self.prefix)
          depfile_works = env.detect_c_compiler(False).get_id() != 'msvc'
  
          self.init(testdir)
          self.build()
          # Immediately rebuilding should not do anything
          self.assertBuildIsNoop()
          # Test compile_resources(depend_file:)
          # Changing mtime of sample.ico should rebuild prog
          self.utime(os.path.join(testdir, 'res', 'sample.ico'))
          self.assertRebuiltTarget('prog')
          # Test depfile generation by compile_resources
          # Changing mtime of resource.h should rebuild myres.rc and then prog
          if depfile_works:
              self.utime(os.path.join(testdir, 'inc', 'resource', 'resource.h'))
              self.assertRebuiltTarget('prog')
          self.wipe()
  
          if depfile_works:
              testdir = os.path.join(self.platform_test_dir, '12 resources with custom targets')
              self.init(testdir)
              self.build()
              # Immediately rebuilding should not do anything
              self.assertBuildIsNoop()
              # Changing mtime of resource.h should rebuild myres_1.rc and then prog_1
              self.utime(os.path.join(testdir, 'res', 'resource.h'))
              self.assertRebuiltTarget('prog_1')
  
  class DarwinTests(BasePlatformTests):
      '''
      Tests that should run on macOS
      '''
      def setUp(self):
          super().setUp()
          self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx')
  
      def test_apple_bitcode(self):
          '''
          Test that -fembed-bitcode is correctly added while compiling and
          -bitcode_bundle is added while linking when b_bitcode 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, '4 shared')
          # Try with bitcode enabled
          out = self.init(testdir, extra_args='-Db_bitcode=true')
          # Warning was printed
          self.assertRegex(out, 'WARNING:.*b_bitcode')
          # Compiler options were added
          compdb = self.get_compdb()
          self.assertIn('-fembed-bitcode', compdb[0]['command'])
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          # Linker options were added
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
          self.assertIsNotNone(m, msg=contents)
          # Try with bitcode disabled
          self.setconf('-Db_bitcode=false')
          # Regenerate build
          self.build()
          compdb = self.get_compdb()
          self.assertNotIn('-fembed-bitcode', compdb[0]['command'])
          build_ninja = os.path.join(self.builddir, 'build.ninja')
          with open(build_ninja, 'r', encoding='utf-8') as f:
              contents = f.read()
              m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
          self.assertIsNone(m, msg=contents)
  
      def test_apple_bitcode_modules(self):
          '''
          Same as above, just for shared_module()
          '''
          testdir = os.path.join(self.common_test_dir, '153 shared module resolving symbol in executable')
          # Ensure that it builds even with bitcode enabled
          self.init(testdir, extra_args='-Db_bitcode=true')
          self.build()
          self.run_tests()
  
      def _get_darwin_versions(self, fname):
          fname = os.path.join(self.builddir, fname)
          out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True)
          m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1])
          self.assertIsNotNone(m, msg=out)
          return m.groups()
  
      def test_library_versioning(self):
          '''
          Ensure that compatibility_version and current_version are set correctly
          '''
          testdir = os.path.join(self.platform_test_dir, '2 library versions')
          self.init(testdir)
          self.build()
          targets = {}
          for t in self.introspect('--targets'):
              targets[t['name']] = t['filename']
          self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0'))
          self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0'))
          self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1'))
  
  
  class LinuxlikeTests(BasePlatformTests):
      '''
      Tests that should run on Linux, macOS, and *BSD
      '''
      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, '25 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.
          '''
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('PIC not relevant')
  
          testdir = os.path.join(self.common_test_dir, '3 static')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertIn('-fPIC', compdb[0]['command'])
          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, '48 pkgconfig-gen')
          self.init(testdir)
          env = get_fake_env(testdir, self.builddir, self.prefix)
          kwargs = {'required': True, 'silent': True}
          os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
          foo_dep = PkgConfigDependency('libfoo', env, kwargs)
          self.assertTrue(foo_dep.found())
          self.assertEqual(foo_dep.get_version(), '1.0')
          self.assertIn('-lfoo', foo_dep.get_link_args())
          self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar')
          self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data')
  
      def test_pkgconfig_gen_deps(self):
          '''
          Test that generated pkg-config files correctly handle dependencies
          '''
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen')
          self.init(testdir)
          privatedir1 = self.privatedir
  
          self.new_builddir()
          os.environ['PKG_CONFIG_LIBDIR'] = privatedir1
          testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen', 'dependencies')
          self.init(testdir)
          privatedir2 = self.privatedir
  
          os.environ['PKG_CONFIG_LIBDIR'] = os.pathsep.join([privatedir1, privatedir2])
          cmd = ['pkg-config', 'dependency-test']
  
          out = self._run(cmd + ['--print-requires']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed']))
  
          out = self._run(cmd + ['--print-requires-private']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libfoo >= 1.0']))
  
          out = self._run(cmd + ['--cflags-only-other']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-DCUSTOM']))
  
          out = self._run(cmd + ['--libs-only-l', '--libs-only-other']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
                                                '-llibmain', '-llibexposed']))
  
          out = self._run(cmd + ['--libs-only-l', '--libs-only-other', '--static']).strip().split()
          self.assertEqual(sorted(out), sorted(['-pthread', '-lcustom',
                                                '-llibmain', '-llibexposed',
                                                '-llibinternal', '-lcustom2',
                                                '-lfoo']))
  
          cmd = ['pkg-config', 'requires-test']
          out = self._run(cmd + ['--print-requires']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
  
          cmd = ['pkg-config', 'requires-private-test']
          out = self._run(cmd + ['--print-requires-private']).strip().split('\n')
          self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
  
      def test_pkg_unfound(self):
          testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig')
          self.init(testdir)
          with open(os.path.join(self.privatedir, 'somename.pc')) as f:
              pcfile = f.read()
          self.assertFalse('blub_blob_blib' in pcfile)
  
      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
          '''
          if not shutil.which('valac'):
              raise unittest.SkipTest('valac not installed.')
          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('GLib.Thread.vala'):
                  continue
              elif each['file'].endswith('retcode.c'):
                  c_command = each['command']
              else:
                  m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file'])
                  raise AssertionError(m)
          self.assertIsNotNone(vala_command)
          self.assertIsNotNone(c_command)
          # -w suppresses all warnings, should be there in Vala but not in C
          self.assertIn(" -w ", vala_command)
          self.assertNotIn(" -w ", c_command)
          # -Wall enables all warnings, should be there in C but not in Vala
          self.assertNotIn(" -Wall ", vala_command)
          self.assertIn(" -Wall ", c_command)
          # -Werror converts warnings to errors, should always be there since it's
          # injected by an unrelated piece of code and the project has werror=true
          self.assertIn(" -Werror ", vala_command)
          self.assertIn(" -Werror ", c_command)
  
      @skipIfNoPkgconfig
      def test_qt5dependency_pkgconfig_detection(self):
          '''
          Test that qt4 and qt5 detection with pkgconfig works.
          '''
          # Verify Qt4 or Qt5 can be found with pkg-config
          qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore'])
          qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core'])
          if qt4 != 0 or qt5 != 0:
              raise unittest.SkipTest('Qt not found with pkg-config')
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir, ['-Dmethod=pkg-config'])
          # Confirm that the dependency was found with pkg-config
          mesonlog = self.get_meson_log()
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt4 \(modules: Core\) found: YES .*, `pkg-config`\n')
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt5 \(modules: Core\) found: YES .*, `pkg-config`\n')
  
      def test_generate_gir_with_address_sanitizer(self):
          if is_cygwin():
              raise unittest.SkipTest('asan not available on Cygwin')
  
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          self.init(testdir, ['-Db_sanitize=address', '-Db_lundef=false'])
          self.build()
  
      def test_qt5dependency_qmake_detection(self):
          '''
          Test that qt5 detection with qmake works. This can't be an ordinary
          test case because it involves setting the environment.
          '''
          # Verify that qmake is for Qt5
          if not shutil.which('qmake-qt5'):
              if not shutil.which('qmake'):
                  raise unittest.SkipTest('QMake not found')
              output = subprocess.getoutput('qmake --version')
              if 'Qt version 5' not in output:
                  raise unittest.SkipTest('Qmake found, but it is not for Qt 5.')
          # Disable pkg-config codepath and force searching with qmake/qmake-qt5
          testdir = os.path.join(self.framework_test_dir, '4 qt')
          self.init(testdir, ['-Dmethod=qmake'])
          # Confirm that the dependency was found with qmake
          mesonlog = self.get_meson_log()
          self.assertRegex('\n'.join(mesonlog),
                           r'Dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n')
  
      def _test_soname_impl(self, libpath, install):
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames')
  
          testdir = os.path.join(self.unit_test_dir, '1 soname')
          self.init(testdir)
          self.build()
          if install:
              self.install()
  
          # File without aliases set.
          nover = os.path.join(libpath, 'libnover.so')
          self.assertPathExists(nover)
          self.assertFalse(os.path.islink(nover))
          self.assertEqual(get_soname(nover), 'libnover.so')
          self.assertEqual(len(glob(nover[:-3] + '*')), 1)
  
          # File with version set
          verset = os.path.join(libpath, 'libverset.so')
          self.assertPathExists(verset + '.4.5.6')
          self.assertEqual(os.readlink(verset), 'libverset.so.4')
          self.assertEqual(get_soname(verset), 'libverset.so.4')
          self.assertEqual(len(glob(verset[:-3] + '*')), 3)
  
          # File with soversion set
          soverset = os.path.join(libpath, 'libsoverset.so')
          self.assertPathExists(soverset + '.1.2.3')
          self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3')
          self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
  
          # File with version and soversion set to same values
          settosame = os.path.join(libpath, 'libsettosame.so')
          self.assertPathExists(settosame + '.7.8.9')
          self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9')
          self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
  
          # File with version and soversion set to different values
          bothset = os.path.join(libpath, 'libbothset.so')
          self.assertPathExists(bothset + '.1.2.3')
          self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
          self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3')
          self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
  
      def test_soname(self):
          self._test_soname_impl(self.builddir, False)
  
      def test_installed_soname(self):
          libdir = self.installdir + os.path.join(self.prefix, self.libdir)
          self._test_soname_impl(libdir, True)
  
      def test_compiler_check_flags_order(self):
          '''
          Test that compiler check flags override all other flags. This can't be
          an ordinary test case because it needs the environment to be set.
          '''
          Oflag = '-O3'
          os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag
          testdir = os.path.join(self.common_test_dir, '40 has function')
          self.init(testdir)
          cmds = self.get_meson_log_compiler_checks()
          for cmd in cmds:
              if cmd[0] == 'ccache':
                  cmd = cmd[1:]
              # Verify that -I flags from the `args` kwarg are first
              # This is set in the '40 has function' test case
              self.assertEqual(cmd[1], '-I/tmp')
              # Verify that -O3 set via the environment is overridden by -O0
              Oargs = [arg for arg in cmd if arg.startswith('-O')]
              self.assertEqual(Oargs, [Oflag, '-O0'])
  
      def _test_stds_impl(self, testdir, compiler, p):
          lang_std = p + '_std'
          # Check that all the listed -std=xxx options for this compiler work
          # just fine when used
          for v in compiler.get_options()[lang_std].choices:
              if (compiler.get_id() == 'clang' and '17' in v and
                  (version_compare(compiler.version, '<5.0.0') or
                   (compiler.clang_type == mesonbuild.compilers.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
                  continue
              if (compiler.get_id() == 'clang' and '2a' in v and
                  (version_compare(compiler.version, '<6.0.0') or
                   (compiler.clang_type == mesonbuild.compilers.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
                  continue
              if (compiler.get_id() == 'gcc' and '2a' in v and version_compare(compiler.version, '<8.0.0')):
                  continue
              std_opt = '{}={}'.format(lang_std, v)
              self.init(testdir, ['-D' + std_opt])
              cmd = self.get_compdb()[0]['command']
              if v != 'none':
                  cmd_std = " -std={} ".format(v)
                  self.assertIn(cmd_std, cmd)
              try:
                  self.build()
              except:
                  print('{} was {!r}'.format(lang_std, v))
                  raise
              self.wipe()
          # Check that an invalid std option in CFLAGS/CPPFLAGS fails
          # Needed because by default ICC ignores invalid options
          cmd_std = '-std=FAIL'
          env_flags = p.upper() + 'FLAGS'
          os.environ[env_flags] = cmd_std
          self.init(testdir)
          cmd = self.get_compdb()[0]['command']
          qcmd_std = " {} ".format(cmd_std)
          self.assertIn(qcmd_std, cmd)
          with self.assertRaises(subprocess.CalledProcessError,
                                 msg='{} should have failed'.format(qcmd_std)):
              self.build()
  
      def test_compiler_c_stds(self):
          '''
          Test that C stds specified for this compiler can all be used. Can't be
          an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cc = env.detect_c_compiler(False)
          self._test_stds_impl(testdir, cc, 'c')
  
      def test_compiler_cpp_stds(self):
          '''
          Test that C++ stds specified for this compiler can all be used. Can't
          be an ordinary test because it requires passing options to meson.
          '''
          testdir = os.path.join(self.common_test_dir, '2 cpp')
          env = get_fake_env(testdir, self.builddir, self.prefix)
          cpp = env.detect_cpp_compiler(False)
          self._test_stds_impl(testdir, cpp, 'cpp')
  
      def test_unity_subproj(self):
          testdir = os.path.join(self.common_test_dir, '46 subproject')
          self.init(testdir, extra_args='--unity=subprojects')
          self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@simpletest@exe/simpletest-unity.c'))
          self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@sublib@sha/sublib-unity.c'))
          self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c'))
          self.build()
  
      def test_installed_modes(self):
          '''
          Test that files installed by these tests have the correct permissions.
          Can't be an ordinary test because our installed_files.txt is very basic.
          '''
          # Test file modes
          testdir = os.path.join(self.common_test_dir, '12 data')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'etc', 'etcfile.dat')
          found_mode = stat.filemode(os.stat(f).st_mode)
          want_mode = 'rw------T'
          self.assertEqual(want_mode, found_mode[1:])
  
          f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-sr-x'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
              self.assertEqual(0, statf.st_gid)
  
          f = os.path.join(self.installdir, 'usr', 'share', 'progname',
                           'fileobject_datafile.dat')
          orig = os.path.join(testdir, 'fileobject_datafile.dat')
          statf = os.stat(f)
          statorig = os.stat(orig)
          found_mode = stat.filemode(statf.st_mode)
          orig_mode = stat.filemode(statorig.st_mode)
          self.assertEqual(orig_mode[1:], found_mode[1:])
          self.assertEqual(os.getuid(), statf.st_uid)
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_gid)
  
          self.wipe()
          # Test directory modes
          testdir = os.path.join(self.common_test_dir, '63 install subdir')
          self.init(testdir)
          self.install()
  
          f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat')
          statf = os.stat(f)
          found_mode = stat.filemode(statf.st_mode)
          want_mode = 'rwxr-x--t'
          self.assertEqual(want_mode, found_mode[1:])
          if os.getuid() == 0:
              # The chown failed nonfatally if we're not root
              self.assertEqual(0, statf.st_uid)
  
      def test_installed_modes_extended(self):
          '''
          Test that files are installed with correct permissions using install_mode.
          '''
          testdir = os.path.join(self.common_test_dir, '196 install_mode')
          self.init(testdir)
          self.build()
          self.install()
  
          for fsobj, want_mode in [
                  ('bin', 'drwxr-x---'),
                  ('bin/runscript.sh', '-rwxr-sr-x'),
                  ('bin/trivialprog', '-rwxr-sr-x'),
                  ('include', 'drwxr-x---'),
                  ('include/config.h', '-rw-rwSr--'),
                  ('include/rootdir.h', '-r--r--r-T'),
                  ('lib', 'drwxr-x---'),
                  ('lib/libstat.a', '-rw---Sr--'),
                  ('share', 'drwxr-x---'),
                  ('share/man', 'drwxr-x---'),
                  ('share/man/man1', 'drwxr-x---'),
                  ('share/man/man1/foo.1.gz', '-r--r--r-T'),
                  ('share/sub1', 'drwxr-x---'),
                  ('share/sub1/second.dat', '-rwxr-x--t'),
                  ('subdir', 'drwxr-x---'),
                  ('subdir/data.dat', '-rw-rwSr--'),
          ]:
              f = os.path.join(self.installdir, 'usr', *fsobj.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (fsobj, want_mode, found_mode)))
          # Ensure that introspect --installed works on all types of files
          # FIXME: also verify the files list
          self.introspect('--installed')
  
      def test_install_umask(self):
          '''
          Test that files are installed with correct permissions using default
          install umask of 022, regardless of the umask at time the worktree
          was checked out or the build was executed.
          '''
          # Copy source tree to a temporary directory and change permissions
          # there to simulate a checkout with umask 002.
          orig_testdir = os.path.join(self.unit_test_dir, '26 install umask')
          # Create a new testdir under tmpdir.
          tmpdir = os.path.realpath(tempfile.mkdtemp())
          self.addCleanup(windows_proof_rmtree, tmpdir)
          testdir = os.path.join(tmpdir, '26 install umask')
          # Copy the tree using shutil.copyfile, which will use the current umask
          # instead of preserving permissions of the old tree.
          save_umask = os.umask(0o002)
          self.addCleanup(os.umask, save_umask)
          shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile)
          # Preserve the executable status of subdir/sayhello though.
          os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775)
          self.init(testdir)
          # Run the build under a 027 umask now.
          os.umask(0o027)
          self.build()
          # And keep umask 027 for the install step too.
          self.install()
  
          for executable in [
                  'bin/prog',
                  'share/subdir/sayhello',
          ]:
              f = os.path.join(self.installdir, 'usr', *executable.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = '-rwxr-xr-x'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (executable, want_mode, found_mode)))
  
          for directory in [
                  'usr',
                  'usr/bin',
                  'usr/include',
                  'usr/share',
                  'usr/share/man',
                  'usr/share/man/man1',
                  'usr/share/subdir',
          ]:
              f = os.path.join(self.installdir, *directory.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = 'drwxr-xr-x'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected directory %s to have mode %s but found %s instead.' %
                                    (directory, want_mode, found_mode)))
  
          for datafile in [
                  'include/sample.h',
                  'share/datafile.cat',
                  'share/file.dat',
                  'share/man/man1/prog.1.gz',
                  'share/subdir/datafile.dog',
          ]:
              f = os.path.join(self.installdir, 'usr', *datafile.split('/'))
              found_mode = stat.filemode(os.stat(f).st_mode)
              want_mode = '-rw-r--r--'
              self.assertEqual(want_mode, found_mode,
                               msg=('Expected file %s to have mode %s but found %s instead.' %
                                    (datafile, want_mode, found_mode)))
  
      def test_cpp_std_override(self):
          testdir = os.path.join(self.unit_test_dir, '6 std override')
          self.init(testdir)
          compdb = self.get_compdb()
          for i in compdb:
              if 'prog03' in i['file']:
                  c03_comp = i['command']
              if 'prog11' in i['file']:
                  c11_comp = i['command']
              if 'progp' in i['file']:
                  plain_comp = i['command']
          self.assertNotEqual(len(plain_comp), 0)
          self.assertIn('-std=c++03', c03_comp)
          self.assertNotIn('-std=c++11', c03_comp)
          self.assertIn('-std=c++11', c11_comp)
          self.assertNotIn('-std=c++03', c11_comp)
          self.assertNotIn('-std=c++03', plain_comp)
          self.assertNotIn('-std=c++11', plain_comp)
          # Now werror
          self.assertIn('-Werror', plain_comp)
          self.assertNotIn('-Werror', c03_comp)
  
      def test_run_installed(self):
          if is_cygwin() or is_osx():
              raise unittest.SkipTest('LD_LIBRARY_PATH and RPATH not applicable')
  
          testdir = os.path.join(self.unit_test_dir, '7 run installed')
          self.init(testdir)
          self.build()
          self.install()
          installed_exe = os.path.join(self.installdir, 'usr/bin/prog')
          installed_libdir = os.path.join(self.installdir, 'usr/foo')
          installed_lib = os.path.join(installed_libdir, 'libfoo.so')
          self.assertTrue(os.path.isfile(installed_exe))
          self.assertTrue(os.path.isdir(installed_libdir))
          self.assertTrue(os.path.isfile(installed_lib))
          # Must fail when run without LD_LIBRARY_PATH to ensure that
          # rpath has been properly stripped rather than pointing to the builddir.
          self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0)
          # When LD_LIBRARY_PATH is set it should start working.
          # For some reason setting LD_LIBRARY_PATH in os.environ fails
          # when all tests are run (but works when only this test is run),
          # but doing this explicitly works.
          env = os.environ.copy()
          env['LD_LIBRARY_PATH'] = installed_libdir
          self.assertEqual(subprocess.call(installed_exe, env=env), 0)
          # Ensure that introspect --installed works
          installed = self.introspect('--installed')
          for v in installed.values():
              self.assertTrue('prog' in v or 'foo' in v)
  
      @skipIfNoPkgconfig
      def test_order_of_l_arguments(self):
          testdir = os.path.join(self.unit_test_dir, '8 -L -l order')
          os.environ['PKG_CONFIG_PATH'] = testdir
          self.init(testdir)
          # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders
          # the flags before returning them to -Lfoo -Lbar -lfoo -lbar
          # but pkgconf seems to not do that. Sigh. Support both.
          expected_order = [('-L/me/first', '-lfoo1'),
                            ('-L/me/second', '-lfoo2'),
                            ('-L/me/first', '-L/me/second'),
                            ('-lfoo1', '-lfoo2'),
                            ('-L/me/second', '-L/me/third'),
                            ('-L/me/third', '-L/me/fourth',),
                            ('-L/me/third', '-lfoo3'),
                            ('-L/me/fourth', '-lfoo4'),
                            ('-lfoo3', '-lfoo4'),
                            ]
          with open(os.path.join(self.builddir, 'build.ninja')) as ifile:
              for line in ifile:
                  if expected_order[0][0] in line:
                      for first, second in expected_order:
                          self.assertLess(line.index(first), line.index(second))
                      return
          raise RuntimeError('Linker entries not found in the Ninja file.')
  
      def test_introspect_dependencies(self):
          '''
          Tests that mesonintrospect --dependencies returns expected output.
          '''
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          self.init(testdir)
          glib_found = False
          gobject_found = False
          deps = self.introspect('--dependencies')
          self.assertIsInstance(deps, list)
          for dep in deps:
              self.assertIsInstance(dep, dict)
              self.assertIn('name', dep)
              self.assertIn('compile_args', dep)
              self.assertIn('link_args', dep)
              if dep['name'] == 'glib-2.0':
                  glib_found = True
              elif dep['name'] == 'gobject-2.0':
                  gobject_found = True
          self.assertTrue(glib_found)
          self.assertTrue(gobject_found)
          if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0:
              raise unittest.SkipTest('glib >= 2.56.2 needed for the rest')
          targets = self.introspect('--targets')
          docbook_target = None
          for t in targets:
              if t['name'] == 'generated-gdbus-docbook':
                  docbook_target = t
                  break
          self.assertIsInstance(docbook_target, dict)
          ifile = self.introspect(['--target-files', 'generated-gdbus-docbook@cus'])[0]
          self.assertEqual(t['filename'], 'gdbus/generated-gdbus-doc-' + ifile)
  
      def test_build_rpath(self):
          if is_cygwin():
              raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
          testdir = os.path.join(self.unit_test_dir, '10 build_rpath')
          self.init(testdir)
          self.build()
          # C program RPATH
          build_rpath = get_rpath(os.path.join(self.builddir, 'prog'))
          self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
          self.install()
          install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog'))
          self.assertEqual(install_rpath, '/baz')
          # C++ program RPATH
          build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx'))
          self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
          self.install()
          install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx'))
          self.assertEqual(install_rpath, 'baz')
  
      def test_pch_with_address_sanitizer(self):
          if is_cygwin():
              raise unittest.SkipTest('asan not available on Cygwin')
  
          testdir = os.path.join(self.common_test_dir, '13 pch')
          self.init(testdir, ['-Db_sanitize=address'])
          self.build()
          compdb = self.get_compdb()
          for i in compdb:
              self.assertIn("-fsanitize=address", i["command"])
  
      def test_coverage(self):
          gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
          if not gcovr_exe:
              raise unittest.SkipTest('gcovr not found')
          if not shutil.which('genhtml') and not gcovr_new_rootdir:
              raise unittest.SkipTest('genhtml not found and gcovr is too old')
          if 'clang' in os.environ.get('CC', ''):
              # We need to use llvm-cov instead of gcovr with clang
              raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!')
          testdir = os.path.join(self.common_test_dir, '1 trivial')
          self.init(testdir, ['-Db_coverage=true'])
          self.build()
          self.run_tests()
          self.run_target('coverage-html')
  
      def test_cross_find_program(self):
          testdir = os.path.join(self.unit_test_dir, '11 cross prog')
          crossfile = tempfile.NamedTemporaryFile(mode='w')
          print(os.path.join(testdir, 'some_cross_tool.py'))
          crossfile.write('''[binaries]
  c = '/usr/bin/cc'
  ar = '/usr/bin/ar'
  strip = '/usr/bin/ar'
  sometool.py = ['{0}']
  someothertool.py = '{0}'
  
  [properties]
  
  [host_machine]
  system = 'linux'
  cpu_family = 'arm'
  cpu = 'armv7' # Not sure if correct.
  endian = 'little'
  '''.format(os.path.join(testdir, 'some_cross_tool.py')))
          crossfile.flush()
          self.meson_cross_file = crossfile.name
          self.init(testdir)
  
      def test_reconfigure(self):
          testdir = os.path.join(self.unit_test_dir, '13 reconfigure')
          self.init(testdir, ['-Db_coverage=true'], default_args=False)
          self.build('reconfigure')
  
      def test_vala_generated_source_buildir_inside_source_tree(self):
          '''
          Test that valac outputs generated C files in the expected location when
          the builddir is a subdir of the source tree.
          '''
          if not shutil.which('valac'):
              raise unittest.SkipTest('valac not installed.')
  
          testdir = os.path.join(self.vala_test_dir, '8 generated sources')
          newdir = os.path.join(self.builddir, 'srctree')
          shutil.copytree(testdir, newdir)
          testdir = newdir
          # New builddir
          builddir = os.path.join(testdir, 'subdir/_build')
          os.makedirs(builddir, exist_ok=True)
          self.change_builddir(builddir)
          self.init(testdir)
          self.build()
  
      def test_old_gnome_module_codepaths(self):
          '''
          A lot of code in the GNOME module is conditional on the version of the
          glib tools that are installed, and breakages in the old code can slip
          by once the CI has a newer glib version. So we force the GNOME module
          to pretend that it's running on an ancient glib so the fallback code is
          also tested.
          '''
          testdir = os.path.join(self.framework_test_dir, '7 gnome')
          os.environ['MESON_UNIT_TEST_PRETEND_GLIB_OLD'] = "1"
          mesonbuild.modules.gnome.native_glib_version = '2.20'
          self.init(testdir, inprocess=True)
          self.build()
          mesonbuild.modules.gnome.native_glib_version = None
  
      @skipIfNoPkgconfig
      def test_pkgconfig_usage(self):
          testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency')
          testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee')
          if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'],
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL) != 0:
              raise unittest.SkipTest('Glib 2.0 dependency not available.')
          with tempfile.TemporaryDirectory() as tempdirname:
              self.init(testdir1, ['--prefix=' + tempdirname, '--libdir=lib'], default_args=False)
              self.install(use_destdir=False)
              shutil.rmtree(self.builddir)
              os.mkdir(self.builddir)
              pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
              self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc')))
              lib_dir = os.path.join(tempdirname, 'lib')
              os.environ['PKG_CONFIG_PATH'] = pkg_dir
              # Private internal libraries must not leak out.
              pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'])
              self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.')
              # Dependencies must not leak to cflags when building only a shared library.
              pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'])
              self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.')
              # Test that the result is usable.
              self.init(testdir2)
              self.build()
              myenv = os.environ.copy()
              myenv['LD_LIBRARY_PATH'] = lib_dir
              if is_cygwin():
                  bin_dir = os.path.join(tempdirname, 'bin')
                  myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH']
              self.assertTrue(os.path.isdir(lib_dir))
              test_exe = os.path.join(self.builddir, 'pkguser')
              self.assertTrue(os.path.isfile(test_exe))
              subprocess.check_call(test_exe, env=myenv)
  
      @skipIfNoPkgconfig
      def test_pkgconfig_internal_libraries(self):
          '''
          '''
          with tempfile.TemporaryDirectory() as tempdirname:
              # build library
              testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries')
              testdirlib = os.path.join(testdirbase, 'lib')
              self.init(testdirlib, extra_args=['--prefix=' + tempdirname,
                                                '--libdir=lib',
                                                '--default-library=static'], default_args=False)
              self.build()
              self.install(use_destdir=False)
  
              # build user of library
              pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
              os.environ['PKG_CONFIG_PATH'] = pkg_dir
              self.new_builddir()
              self.init(os.path.join(testdirbase, 'app'))
              self.build()
  
      @skipIfNoPkgconfig
      def test_pkgconfig_formatting(self):
          testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format')
          self.init(testdir)
          myenv = os.environ.copy()
          myenv['PKG_CONFIG_PATH'] = self.privatedir
          stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv)
          deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething']
          if is_windows() or is_cygwin() or is_osx():
              # On Windows, libintl is a separate library
              deps.append(b'-lintl')
          self.assertEqual(set(deps), set(stdo.split()))
  
      def test_deterministic_dep_order(self):
          '''
          Test that the dependencies are always listed in a deterministic order.
          '''
          testdir = os.path.join(self.unit_test_dir, '43 dep order')
          self.init(testdir)
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if 'build myexe:' in line or 'build myexe.exe:' in line:
                      self.assertIn('liblib1.a liblib2.a', line)
                      return
          raise RuntimeError('Could not find the build rule')
  
      def test_deterministic_rpath_order(self):
          '''
          Test that the rpaths are always listed in a deterministic order.
          '''
          if is_cygwin():
              raise unittest.SkipTest('rpath are not used on Cygwin')
          testdir = os.path.join(self.unit_test_dir, '42 rpath order')
          self.init(testdir)
          if is_osx():
              rpathre = re.compile('-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2')
          else:
              rpathre = re.compile('-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2')
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if '-rpath' in line:
                      self.assertRegex(line, rpathre)
                      return
          raise RuntimeError('Could not find the rpath')
  
      def test_override_with_exe_dep(self):
          '''
          Test that we produce the correct dependencies when a program is overridden with an executable.
          '''
          testdir = os.path.join(self.common_test_dir, '202 override with exe')
          self.init(testdir)
          with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
              for line in bfile:
                  if 'main1.c:' in line or 'main2.c:' in line:
                      self.assertIn('| subprojects/sub/foobar', line)
  
      @skipIfNoPkgconfig
      def test_usage_external_library(self):
          '''
          Test that uninstalled usage of an external library (from the system or
          PkgConfigDependency) works. On macOS, this workflow works out of the
          box. On Linux, BSDs, Windows, etc, you need to set extra arguments such
          as LD_LIBRARY_PATH, etc, so this test is skipped.
  
          The system library is found with cc.find_library() and pkg-config deps.
          '''
          oldprefix = self.prefix
          # Install external library so we can find it
          testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library')
          # install into installdir without using DESTDIR
          installdir = self.installdir
          self.prefix = installdir
          self.init(testdir)
          self.prefix = oldprefix
          self.build()
          self.install(use_destdir=False)
          ## New builddir for the consumer
          self.new_builddir()
          os.environ['LIBRARY_PATH'] = os.path.join(installdir, self.libdir)
          os.environ['PKG_CONFIG_PATH'] = os.path.join(installdir, self.libdir, 'pkgconfig')
          testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library')
          # install into installdir without using DESTDIR
          self.prefix = self.installdir
          self.init(testdir)
          self.prefix = oldprefix
          self.build()
          # test uninstalled
          self.run_tests()
          if not is_osx():
              # Rest of the workflow only works on macOS
              return
          # test running after installation
          self.install(use_destdir=False)
          prog = os.path.join(self.installdir, 'bin', 'prog')
          self._run([prog])
          out = self._run(['otool', '-L', prog])
          self.assertNotIn('@rpath', out)
          ## New builddir for testing that DESTDIR is not added to install_name
          self.new_builddir()
          # install into installdir with DESTDIR
          self.init(testdir)
          self.build()
          # test running after installation
          self.install()
          prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog')
          lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib')
          for f in prog, lib:
              out = self._run(['otool', '-L', f])
              # Ensure that the otool output does not contain self.installdir
              self.assertNotRegex(out, self.installdir + '.*dylib ')
  
      def install_subdir_invalid_symlinks(self, testdir, subdir_path):
          '''
          Test that installation of broken symlinks works fine.
          https://github.com/mesonbuild/meson/issues/3914
          '''
          testdir = os.path.join(self.common_test_dir, testdir)
          subdir = os.path.join(testdir, subdir_path)
          curdir = os.getcwd()
          os.chdir(subdir)
          # Can't distribute broken symlinks in the source tree because it breaks
          # the creation of zipapps. Create it dynamically and run the test by
          # hand.
          src = '../../nonexistent.txt'
          os.symlink(src, 'invalid-symlink.txt')
          try:
              self.init(testdir)
              self.build()
              self.install()
              install_path = subdir_path.split(os.path.sep)[-1]
              link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt')
              self.assertTrue(os.path.islink(link), msg=link)
              self.assertEqual(src, os.readlink(link))
              self.assertFalse(os.path.isfile(link), msg=link)
          finally:
              os.remove(os.path.join(subdir, 'invalid-symlink.txt'))
              os.chdir(curdir)
  
      def test_install_subdir_symlinks(self):
          self.install_subdir_invalid_symlinks('63 install subdir', os.path.join('sub', 'sub1'))
  
      def test_install_subdir_symlinks_with_default_umask(self):
          self.install_subdir_invalid_symlinks('196 install_mode', 'sub2')
  
      def test_install_subdir_symlinks_with_default_umask_and_mode(self):
          self.install_subdir_invalid_symlinks('196 install_mode', 'sub1')
  
  
  class LinuxCrossArmTests(BasePlatformTests):
      '''
      Tests that cross-compilation to Linux/ARM works
      '''
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt')
  
      def test_cflags_cross_environment_pollution(self):
          '''
          Test that the CFLAGS environment variable does not pollute the cross
          environment. 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')
          os.environ['CFLAGS'] = '-DBUILD_ENVIRONMENT_ONLY'
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command'])
  
      def test_cross_file_overrides_always_args(self):
          '''
          Test that $lang_args in cross files always override get_always_args().
          Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some
          architectures such as some Android versions and Raspbian.
          https://github.com/mesonbuild/meson/issues/3049
          https://github.com/mesonbuild/meson/issues/3089
          '''
          testdir = os.path.join(self.unit_test_dir, '33 cross file overrides always args')
          self.meson_cross_file = os.path.join(testdir, 'ubuntu-armhf-overrides.txt')
          self.init(testdir)
          compdb = self.get_compdb()
          self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS')
          self.build()
  
  class LinuxCrossMingwTests(BasePlatformTests):
      '''
      Tests that cross-compilation to Windows/MinGW works
      '''
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt')
  
      def test_exe_wrapper_behaviour(self):
          '''
          Test that an exe wrapper that isn't found doesn't cause compiler sanity
          checks and compiler checks to fail, but causes configure to fail if it
          requires running a cross-built executable (custom_target or run_target)
          and causes the tests to be skipped if they are run.
          '''
          testdir = os.path.join(self.unit_test_dir, '36 exe_wrapper behaviour')
          # Configures, builds, and tests fine by default
          self.init(testdir)
          self.build()
          self.run_tests()
          self.wipe()
          os.mkdir(self.builddir)
          # Change cross file to use a non-existing exe_wrapper and it should fail
          self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt')
          # Force tracebacks so we can detect them properly
          os.environ['MESON_FORCE_BACKTRACE'] = '1'
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(testdir, extra_args='-Drun-target=false', inprocess=True)
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.init(testdir, extra_args='-Dcustom-target=false', inprocess=True)
          self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'])
          self.build()
          with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'):
              # Must run in-process or we'll get a generic CalledProcessError
              self.run_tests(inprocess=True)
  
  
  class PythonTests(BasePlatformTests):
      '''
      Tests that verify compilation of python extension modules
      '''
      def test_versions(self):
          if self.backend is not Backend.ninja:
              raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name))
  
          testdir = os.path.join(self.src_root, 'test cases', 'unit', '39 python extmodule')
  
          # No python version specified, this will use meson's python
          self.init(testdir)
          self.build()
          self.run_tests()
          self.wipe()
  
          # When specifying a known name, (python2 / python3) the module
          # will also try 'python' as a fallback and use it if the major
          # version matches
          try:
              self.init(testdir, ['-Dpython=python2'])
              self.build()
              self.run_tests()
          except unittest.SkipTest:
              # python2 is not necessarily installed on the test machine,
              # if it is not, or the python headers can't be found, the test
              # will raise MESON_SKIP_TEST, we could check beforehand what version
              # of python is available, but it's a bit of a chicken and egg situation,
              # as that is the job of the module, so we just ask for forgiveness rather
              # than permission.
              pass
  
          self.wipe()
  
          for py in ('pypy', 'pypy3'):
              try:
                  self.init(testdir, ['-Dpython=%s' % py])
              except unittest.SkipTest:
                  # Same as above, pypy2 and pypy3 are not expected to be present
                  # on the test system, the test project only raises in these cases
                  continue
  
              # We have a pypy, this is expected to work
              self.build()
              self.run_tests()
              self.wipe()
  
          # The test is configured to error out with MESON_SKIP_TEST
          # in case it could not find python
          with self.assertRaises(unittest.SkipTest):
              self.init(testdir, ['-Dpython=not-python'])
          self.wipe()
  
          # While dir is an external command on both Windows and Linux,
          # it certainly isn't python
          with self.assertRaises(unittest.SkipTest):
              self.init(testdir, ['-Dpython=dir'])
          self.wipe()
  
  
  class RewriterTests(unittest.TestCase):
  
      def setUp(self):
          super().setUp()
          src_root = os.path.dirname(__file__)
          self.testroot = os.path.realpath(tempfile.mkdtemp())
          self.rewrite_command = python_command + [os.path.join(src_root, 'mesonrewriter.py')]
          self.tmpdir = os.path.realpath(tempfile.mkdtemp())
          self.workdir = os.path.join(self.tmpdir, 'foo')
          self.test_dir = os.path.join(src_root, 'test cases/rewrite')
  
      def tearDown(self):
          windows_proof_rmtree(self.tmpdir)
  
      def read_contents(self, fname):
          with open(os.path.join(self.workdir, fname)) as f:
              return f.read()
  
      def check_effectively_same(self, mainfile, truth):
          mf = self.read_contents(mainfile)
          t = self.read_contents(truth)
          # Rewriting is not guaranteed to do a perfect job of
          # maintaining whitespace.
          self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
  
      def prime(self, dirname):
          shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
  
      def test_basic(self):
          self.prime('1 basic')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'removed.txt')
          subprocess.check_call(self.rewrite_command + ['add',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'added.txt')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=trivialprog',
                                                        '--filename=notthere.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('meson.build', 'removed.txt')
  
      def test_subdir(self):
          self.prime('2 subdirs')
          top = self.read_contents('meson.build')
          s2 = self.read_contents('sub2/meson.build')
          subprocess.check_call(self.rewrite_command + ['remove',
                                                        '--target=something',
                                                        '--filename=second.c',
                                                        '--sourcedir', self.workdir],
                                universal_newlines=True)
          self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
          self.assertEqual(top, self.read_contents('meson.build'))
          self.assertEqual(s2, self.read_contents('sub2/meson.build'))
  
  
  def unset_envs():
      # For unit tests we must fully control all command lines
      # so that there are no unexpected changes coming from the
      # environment, for example when doing a package build.
      varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.cflags_mapping.values())
      for v in varnames:
          if v in os.environ:
              del os.environ[v]
  
  def should_run_cross_arm_tests():
      return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
  
  def should_run_cross_mingw_tests():
      return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin())
  
  if __name__ == '__main__':
      unset_envs()
      cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 'PythonTests']
      if not is_windows():
          cases += ['LinuxlikeTests']
          if should_run_cross_arm_tests():
              cases += ['LinuxCrossArmTests']
          if should_run_cross_mingw_tests():
              cases += ['LinuxCrossMingwTests']
      if is_windows() or is_cygwin():
          cases += ['WindowsTests']
      if is_osx():
          cases += ['DarwinTests']
  
      unittest.main(defaultTest=cases, buffer=True)
  
files: run_unittests.py