files: # Copyright 2013-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # This file contains the detection logic for external
  # dependencies. Mostly just uses pkg-config but also contains
  # custom logic for packages that don't provide them.
  
  # Currently one file, should probably be split into a
  # package before this gets too big.
  
  import re
  import sys
  import os, stat, glob, shutil
  import subprocess
  import sysconfig
  from enum import Enum
  from collections import OrderedDict
  from . mesonlib import MesonException, version_compare, version_compare_many, Popen_safe
  from . import mlog
  from . import mesonlib
  from .environment import detect_cpu_family, for_windows
  
  class DependencyException(MesonException):
      '''Exceptions raised while trying to find dependencies'''
  
  class DependencyMethods(Enum):
      # Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best.
      AUTO = 'auto'
      PKGCONFIG = 'pkg-config'
      QMAKE = 'qmake'
      # Just specify the standard link arguments, assuming the operating system provides the library.
      SYSTEM = 'system'
      # Detect using sdl2-config
      SDLCONFIG = 'sdlconfig'
      # This is only supported on OSX - search the frameworks directory by name.
      EXTRAFRAMEWORK = 'extraframework'
      # Detect using the sysconfig module.
      SYSCONFIG = 'sysconfig'
  
  class Dependency:
      def __init__(self, type_name, kwargs):
          self.name = "null"
          self.is_found = False
          self.type_name = type_name
          method = DependencyMethods(kwargs.get('method', 'auto'))
  
          # Set the detection method. If the method is set to auto, use any available method.
          # If method is set to a specific string, allow only that detection method.
          if method == DependencyMethods.AUTO:
              self.methods = self.get_methods()
          elif method in self.get_methods():
              self.methods = [method]
          else:
              raise MesonException('Unsupported detection method: {}, allowed methods are {}'.format(method.value, mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods()))))
  
      def __repr__(self):
          s = '<{0} {1}: {2}>'
          return s.format(self.__class__.__name__, self.name, self.is_found)
  
      def get_compile_args(self):
          return []
  
      def get_link_args(self):
          return []
  
      def found(self):
          return self.is_found
  
      def get_sources(self):
          """Source files that need to be added to the target.
          As an example, gtest-all.cc when using GTest."""
          return []
  
      def get_methods(self):
          return [DependencyMethods.AUTO]
  
      def get_name(self):
          return self.name
  
      def get_exe_args(self, compiler):
          return []
  
      def need_threads(self):
          return False
  
      def get_pkgconfig_variable(self, variable_name):
          raise MesonException('Tried to get a pkg-config variable from a non-pkgconfig dependency.')
  
  class InternalDependency(Dependency):
      def __init__(self, version, incdirs, compile_args, link_args, libraries, sources, ext_deps):
          super().__init__('internal', {})
          self.version = version
          self.include_directories = incdirs
          self.compile_args = compile_args
          self.link_args = link_args
          self.libraries = libraries
          self.sources = sources
          self.ext_deps = ext_deps
  
      def get_compile_args(self):
          return self.compile_args
  
      def get_link_args(self):
          return self.link_args
  
      def get_version(self):
          return self.version
  
  class PkgConfigDependency(Dependency):
      # The class's copy of the pkg-config path. Avoids having to search for it
      # multiple times in the same Meson invocation.
      class_pkgbin = None
  
      def __init__(self, name, environment, kwargs):
          Dependency.__init__(self, 'pkgconfig', kwargs)
          self.is_libtool = False
          self.required = kwargs.get('required', True)
          self.static = kwargs.get('static', False)
          self.silent = kwargs.get('silent', False)
          if not isinstance(self.static, bool):
              raise DependencyException('Static keyword must be boolean')
          # Store a copy of the pkg-config path on the object itself so it is
          # stored in the pickled coredata and recovered.
          self.pkgbin = None
          self.cargs = []
          self.libs = []
          if 'native' in kwargs and environment.is_cross_build():
              self.want_cross = not kwargs['native']
          else:
              self.want_cross = environment.is_cross_build()
          self.name = name
          self.modversion = 'none'
  
          # When finding dependencies for cross-compiling, we don't care about
          # the 'native' pkg-config
          if self.want_cross:
              if 'pkgconfig' not in environment.cross_info.config['binaries']:
                  if self.required:
                      raise DependencyException('Pkg-config binary missing from cross file')
              else:
                  pkgname = environment.cross_info.config['binaries']['pkgconfig']
                  potential_pkgbin = ExternalProgram(pkgname, silent=True)
                  if potential_pkgbin.found():
                      # FIXME, we should store all pkg-configs in ExternalPrograms.
                      # However that is too destabilizing a change to do just before release.
                      self.pkgbin = potential_pkgbin.get_command()[0]
                      PkgConfigDependency.class_pkgbin = self.pkgbin
                  else:
                      mlog.debug('Cross pkg-config %s not found.' % potential_pkgbin.name)
          # Only search for the native pkg-config the first time and
          # store the result in the class definition
          elif PkgConfigDependency.class_pkgbin is None:
              self.pkgbin = self.check_pkgconfig()
              PkgConfigDependency.class_pkgbin = self.pkgbin
          else:
              self.pkgbin = PkgConfigDependency.class_pkgbin
  
          self.is_found = False
          if not self.pkgbin:
              if self.required:
                  raise DependencyException('Pkg-config not found.')
              return
          if self.want_cross:
              self.type_string = 'Cross'
          else:
              self.type_string = 'Native'
  
          mlog.debug('Determining dependency {!r} with pkg-config executable '
                     '{!r}'.format(name, self.pkgbin))
          ret, self.modversion = self._call_pkgbin(['--modversion', name])
          if ret != 0:
              if self.required:
                  raise DependencyException('{} dependency {!r} not found'
                                            ''.format(self.type_string, name))
              return
          found_msg = [self.type_string + ' dependency', mlog.bold(name), 'found:']
          self.version_reqs = kwargs.get('version', None)
          if self.version_reqs is None:
              self.is_found = True
          else:
              if not isinstance(self.version_reqs, (str, list)):
                  raise DependencyException('Version argument must be string or list.')
              if isinstance(self.version_reqs, str):
                  self.version_reqs = [self.version_reqs]
              (self.is_found, not_found, found) = \
                  version_compare_many(self.modversion, self.version_reqs)
              if not self.is_found:
                  found_msg += [mlog.red('NO'),
                                'found {!r} but need:'.format(self.modversion),
                                ', '.join(["'{}'".format(e) for e in not_found])]
                  if found:
                      found_msg += ['; matched:',
                                    ', '.join(["'{}'".format(e) for e in found])]
                  if not self.silent:
                      mlog.log(*found_msg)
                  if self.required:
                      m = 'Invalid version of dependency, need {!r} {!r} found {!r}.'
                      raise DependencyException(m.format(name, not_found, self.modversion))
                  return
          found_msg += [mlog.green('YES'), self.modversion]
          # Fetch cargs to be used while using this dependency
          self._set_cargs()
          # Fetch the libraries and library paths needed for using this
          self._set_libs()
          # Print the found message only at the very end because fetching cflags
          # and libs can also fail if other needed pkg-config files aren't found.
          if not self.silent:
              mlog.log(*found_msg)
  
      def __repr__(self):
          s = '<{0} {1}: {2} {3}>'
          return s.format(self.__class__.__name__, self.name, self.is_found,
                          self.version_reqs)
  
      def _call_pkgbin(self, args):
          p, out = Popen_safe([self.pkgbin] + args, env=os.environ)[0:2]
          return p.returncode, out.strip()
  
      def _set_cargs(self):
          ret, out = self._call_pkgbin(['--cflags', self.name])
          if ret != 0:
              raise DependencyException('Could not generate cargs for %s:\n\n%s' %
                                        (self.name, out))
          self.cargs = out.split()
  
      def _set_libs(self):
          libcmd = [self.name, '--libs']
          if self.static:
              libcmd.append('--static')
          ret, out = self._call_pkgbin(libcmd)
          if ret != 0:
              raise DependencyException('Could not generate libs for %s:\n\n%s' %
                                        (self.name, out))
          self.libs = []
          for lib in out.split():
              if lib.endswith(".la"):
                  shared_libname = self.extract_libtool_shlib(lib)
                  shared_lib = os.path.join(os.path.dirname(lib), shared_libname)
                  if not os.path.exists(shared_lib):
                      shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname)
  
                  if not os.path.exists(shared_lib):
                      raise DependencyException('Got a libtools specific "%s" dependencies'
                                                'but we could not compute the actual shared'
                                                'library path' % lib)
                  lib = shared_lib
                  self.is_libtool = True
              self.libs.append(lib)
  
      def get_pkgconfig_variable(self, variable_name):
          ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name])
          variable = ''
          if ret != 0:
              if self.required:
                  raise DependencyException('%s dependency %s not found.' %
                                            (self.type_string, self.name))
          else:
              variable = out.strip()
          mlog.debug('Got pkgconfig variable %s : %s' % (variable_name, variable))
          return variable
  
      def get_modversion(self):
          return self.modversion
  
      def get_version(self):
          return self.modversion
  
      def get_compile_args(self):
          return self.cargs
  
      def get_link_args(self):
          return self.libs
  
      def get_methods(self):
          return [DependencyMethods.PKGCONFIG]
  
      def check_pkgconfig(self):
          evar = 'PKG_CONFIG'
          if evar in os.environ:
              pkgbin = os.environ[evar].strip()
          else:
              pkgbin = 'pkg-config'
          try:
              p, out = Popen_safe([pkgbin, '--version'])[0:2]
              if p.returncode != 0:
                  # Set to False instead of None to signify that we've already
                  # searched for it and not found it
                  pkgbin = False
          except (FileNotFoundError, PermissionError):
              pkgbin = False
          if pkgbin and not os.path.isabs(pkgbin) and shutil.which(pkgbin):
              # Sometimes shutil.which fails where Popen succeeds, so
              # only find the abs path if it can be found by shutil.which
              pkgbin = shutil.which(pkgbin)
          if not self.silent:
              if pkgbin:
                  mlog.log('Found pkg-config:', mlog.bold(pkgbin),
                           '(%s)' % out.strip())
              else:
                  mlog.log('Found Pkg-config:', mlog.red('NO'))
          return pkgbin
  
      def found(self):
          return self.is_found
  
      def extract_field(self, la_file, fieldname):
          with open(la_file) as f:
              for line in f:
                  arr = line.strip().split('=')
                  if arr[0] == fieldname:
                      return arr[1][1:-1]
          return None
  
      def extract_dlname_field(self, la_file):
          return self.extract_field(la_file, 'dlname')
  
      def extract_libdir_field(self, la_file):
          return self.extract_field(la_file, 'libdir')
  
      def extract_libtool_shlib(self, la_file):
          '''
          Returns the path to the shared library
          corresponding to this .la file
          '''
          dlname = self.extract_dlname_field(la_file)
          if dlname is None:
              return None
  
          # Darwin uses absolute paths where possible; since the libtool files never
          # contain absolute paths, use the libdir field
          if mesonlib.is_osx():
              dlbasename = os.path.basename(dlname)
              libdir = self.extract_libdir_field(la_file)
              if libdir is None:
                  return dlbasename
              return os.path.join(libdir, dlbasename)
          # From the comments in extract_libtool(), older libtools had
          # a path rather than the raw dlname
          return os.path.basename(dlname)
  
  class WxDependency(Dependency):
      wx_found = None
  
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'wx', kwargs)
          self.is_found = False
          self.modversion = 'none'
          if WxDependency.wx_found is None:
              self.check_wxconfig()
          if not WxDependency.wx_found:
              mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency")
              return
  
          p, out = Popen_safe([self.wxc, '--version'])[0:2]
          if p.returncode != 0:
              mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
              self.cargs = []
              self.libs = []
          else:
              self.modversion = out.strip()
              version_req = kwargs.get('version', None)
              if version_req is not None:
                  if not version_compare(self.modversion, version_req, strict=True):
                      mlog.log('Wxwidgets version %s does not fullfill requirement %s' %
                               (self.modversion, version_req))
                      return
              mlog.log('Dependency wxwidgets found:', mlog.green('YES'))
              self.is_found = True
              self.requested_modules = self.get_requested(kwargs)
              # wx-config seems to have a cflags as well but since it requires C++,
              # this should be good, at least for now.
              p, out = Popen_safe([self.wxc, '--cxxflags'])[0:2]
              if p.returncode != 0:
                  raise DependencyException('Could not generate cargs for wxwidgets.')
              self.cargs = out.split()
  
              p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2]
              if p.returncode != 0:
                  raise DependencyException('Could not generate libs for wxwidgets.')
              self.libs = out.split()
  
      def get_requested(self, kwargs):
          modules = 'modules'
          if modules not in kwargs:
              return []
          candidates = kwargs[modules]
          if isinstance(candidates, str):
              return [candidates]
          for c in candidates:
              if not isinstance(c, str):
                  raise DependencyException('wxwidgets module argument is not a string.')
          return candidates
  
      def get_modversion(self):
          return self.modversion
  
      def get_version(self):
          return self.modversion
  
      def get_compile_args(self):
          return self.cargs
  
      def get_link_args(self):
          return self.libs
  
      def check_wxconfig(self):
          for wxc in ['wx-config-3.0', 'wx-config']:
              try:
                  p, out = Popen_safe([wxc, '--version'])[0:2]
                  if p.returncode == 0:
                      mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
                               '(%s)' % out.strip())
                      self.wxc = wxc
                      WxDependency.wx_found = True
                      return
              except (FileNotFoundError, PermissionError):
                  pass
          WxDependency.wxconfig_found = False
          mlog.log('Found wx-config:', mlog.red('NO'))
  
      def found(self):
          return self.is_found
  
  class ExternalProgram:
      windows_exts = ('exe', 'msc', 'com', 'bat')
  
      def __init__(self, name, command=None, silent=False, search_dir=None):
          self.name = name
          if command is not None:
              if not isinstance(command, list):
                  self.command = [command]
              else:
                  self.command = command
          else:
              self.command = self._search(name, search_dir)
          if not silent:
              if self.found():
                  mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
                           '(%s)' % ' '.join(self.command))
              else:
                  mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
  
      def __repr__(self):
          r = '<{} {!r} -> {!r}>'
          return r.format(self.__class__.__name__, self.name, self.command)
  
      @staticmethod
      def _shebang_to_cmd(script):
          """
          Windows does not understand shebangs, so we check if the file has a
          shebang and manually parse it to figure out the interpreter to use
          """
          try:
              with open(script) as f:
                  first_line = f.readline().strip()
              if first_line.startswith('#!'):
                  commands = first_line[2:].split('#')[0].strip().split()
                  if mesonlib.is_windows():
                      # Windows does not have UNIX paths so remove them,
                      # but don't remove Windows paths
                      if commands[0].startswith('/'):
                          commands[0] = commands[0].split('/')[-1]
                      if len(commands) > 0 and commands[0] == 'env':
                          commands = commands[1:]
                      # Windows does not ship python3.exe, but we know the path to it
                      if len(commands) > 0 and commands[0] == 'python3':
                          commands[0] = sys.executable
                  return commands + [script]
          except Exception:
              pass
          return False
  
      def _is_executable(self, path):
          suffix = os.path.splitext(path)[-1].lower()[1:]
          if mesonlib.is_windows():
              if suffix in self.windows_exts:
                  return True
          elif os.access(path, os.X_OK):
              return not os.path.isdir(path)
          return False
  
      def _search_dir(self, name, search_dir):
          if search_dir is None:
              return False
          trial = os.path.join(search_dir, name)
          if os.path.exists(trial):
              if self._is_executable(trial):
                  return [trial]
          else:
              for ext in self.windows_exts:
                  trial_ext = '{}.{}'.format(trial, ext)
                  if os.path.exists(trial_ext):
                      return [trial_ext]
              return False
          # Now getting desperate. Maybe it is a script file that is a) not chmodded
          # executable or b) we are on windows so they can't be directly executed.
          return self._shebang_to_cmd(trial)
  
      def _search(self, name, search_dir):
          '''
          Search in the specified dir for the specified executable by name
          and if not found search in PATH
          '''
          commands = self._search_dir(name, search_dir)
          if commands:
              return commands
          # Do a standard search in PATH
          command = shutil.which(name)
          if not mesonlib.is_windows():
              # On UNIX-like platforms, the standard PATH search is enough
              return [command]
          # HERE BEGINS THE TERROR OF WINDOWS
          if command:
              # On Windows, even if the PATH search returned a full path, we can't be
              # sure that it can be run directly if it's not a native executable.
              # For instance, interpreted scripts sometimes need to be run explicitly
              # with an interpreter if the file association is not done properly.
              name_ext = os.path.splitext(command)[1]
              if name_ext[1:].lower() in self.windows_exts:
                  # Good, it can be directly executed
                  return [command]
              # Try to extract the interpreter from the shebang
              commands = self._shebang_to_cmd(command)
              if commands:
                  return commands
          else:
              # Maybe the name is an absolute path to a native Windows
              # executable, but without the extension. This is technically wrong,
              # but many people do it because it works in the MinGW shell.
              if os.path.isabs(name):
                  for ext in self.windows_exts:
                      command = '{}.{}'.format(name, ext)
                      if os.path.exists(command):
                          return [command]
              # On Windows, interpreted scripts must have an extension otherwise they
              # cannot be found by a standard PATH search. So we do a custom search
              # where we manually search for a script with a shebang in PATH.
              search_dirs = os.environ.get('PATH', '').split(';')
              for search_dir in search_dirs:
                  commands = self._search_dir(name, search_dir)
                  if commands:
                      return commands
          return [None]
  
      def found(self):
          return self.command[0] is not None
  
      def get_command(self):
          return self.command[:]
  
      def get_path(self):
          # Assume that the last element is the full path to the script
          # If it's not a script, this will be an array of length 1
          if self.found():
              return self.command[-1]
          return None
  
      def get_name(self):
          return self.name
  
  class ExternalLibrary(Dependency):
      # TODO: Add `lang` to the parent Dependency object so that dependencies can
      # be expressed for languages other than C-like
      def __init__(self, name, link_args=None, language=None, silent=False):
          super().__init__('external', {})
          self.name = name
          self.is_found = False
          self.link_args = []
          self.lang_args = []
          if link_args:
              self.is_found = True
              if not isinstance(link_args, list):
                  link_args = [link_args]
              if language:
                  self.lang_args = {language: link_args}
              else:
                  self.link_args = link_args
          if not silent:
              if self.is_found:
                  mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'))
              else:
                  mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
  
      def found(self):
          return self.is_found
  
      def get_name(self):
          return self.name
  
      def get_link_args(self):
          return self.link_args
  
      def get_lang_args(self, lang):
          if lang in self.lang_args:
              return self.lang_args[lang]
          return []
  
  class BoostDependency(Dependency):
      # Some boost libraries have different names for
      # their sources and libraries. This dict maps
      # between the two.
      name2lib = {'test': 'unit_test_framework'}
  
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'boost', kwargs)
          self.name = 'boost'
          self.environment = environment
          self.libdir = ''
          if 'native' in kwargs and environment.is_cross_build():
              self.want_cross = not kwargs['native']
          else:
              self.want_cross = environment.is_cross_build()
          try:
              self.boost_root = os.environ['BOOST_ROOT']
              if not os.path.isabs(self.boost_root):
                  raise DependencyException('BOOST_ROOT must be an absolute path.')
          except KeyError:
              self.boost_root = None
          if self.boost_root is None:
              if self.want_cross:
                  if 'BOOST_INCLUDEDIR' in os.environ:
                      self.incdir = os.environ['BOOST_INCLUDEDIR']
                  else:
                      raise DependencyException('BOOST_ROOT or BOOST_INCLUDEDIR is needed while cross-compiling')
              if mesonlib.is_windows():
                  self.boost_root = self.detect_win_root()
                  self.incdir = self.boost_root
              else:
                  if 'BOOST_INCLUDEDIR' in os.environ:
                      self.incdir = os.environ['BOOST_INCLUDEDIR']
                  else:
                      self.incdir = '/usr/include'
          else:
              self.incdir = os.path.join(self.boost_root, 'include')
          self.boost_inc_subdir = os.path.join(self.incdir, 'boost')
          mlog.debug('Boost library root dir is', self.boost_root)
          self.src_modules = {}
          self.lib_modules = {}
          self.lib_modules_mt = {}
          self.detect_version()
          self.requested_modules = self.get_requested(kwargs)
          module_str = ', '.join(self.requested_modules)
          if self.version is not None:
              self.detect_src_modules()
              self.detect_lib_modules()
              self.validate_requested()
              if self.boost_root is not None:
                  info = self.version + ', ' + self.boost_root
              else:
                  info = self.version
              mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info)
          else:
              mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO'))
          if 'cpp' not in self.environment.coredata.compilers:
              raise DependencyException('Tried to use Boost but a C++ compiler is not defined.')
          self.cpp_compiler = self.environment.coredata.compilers['cpp']
  
      def detect_win_root(self):
          globtext = 'c:\\local\\boost_*'
          files = glob.glob(globtext)
          if len(files) > 0:
              return files[0]
          return 'C:\\'
  
      def get_compile_args(self):
          args = []
          if self.boost_root is not None:
              if mesonlib.is_windows():
                  args.append('-I' + self.boost_root)
              else:
                  args.append('-I' + os.path.join(self.boost_root, 'include'))
          else:
              args.append('-I' + self.incdir)
          return args
  
      def get_requested(self, kwargs):
          candidates = kwargs.get('modules', [])
          if isinstance(candidates, str):
              return [candidates]
          for c in candidates:
              if not isinstance(c, str):
                  raise DependencyException('Boost module argument is not a string.')
          return candidates
  
      def validate_requested(self):
          for m in self.requested_modules:
              if m not in self.src_modules:
                  raise DependencyException('Requested Boost module "%s" not found.' % m)
  
      def found(self):
          return self.version is not None
  
      def get_version(self):
          return self.version
  
      def detect_version(self):
          try:
              ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp'))
          except FileNotFoundError:
              self.version = None
              return
          with ifile:
              for line in ifile:
                  if line.startswith("#define") and 'BOOST_LIB_VERSION' in line:
                      ver = line.split()[-1]
                      ver = ver[1:-1]
                      self.version = ver.replace('_', '.')
                      return
          self.version = None
  
      def detect_src_modules(self):
          for entry in os.listdir(self.boost_inc_subdir):
              entry = os.path.join(self.boost_inc_subdir, entry)
              if stat.S_ISDIR(os.stat(entry).st_mode):
                  self.src_modules[os.path.split(entry)[-1]] = True
  
      def detect_lib_modules(self):
          if mesonlib.is_windows():
              return self.detect_lib_modules_win()
          return self.detect_lib_modules_nix()
  
      def detect_lib_modules_win(self):
          arch = detect_cpu_family(self.environment.coredata.compilers)
          # Guess the libdir
          if arch == 'x86':
              gl = 'lib32*'
          elif arch == 'x86_64':
              gl = 'lib64*'
          else:
              # Does anyone do Boost cross-compiling to other archs on Windows?
              gl = None
          # See if the libdir is valid
          if gl:
              libdir = glob.glob(os.path.join(self.boost_root, gl))
          else:
              libdir = []
          # Can't find libdir, bail
          if len(libdir) == 0:
              return
          libdir = libdir[0]
          self.libdir = libdir
          globber = 'boost_*-gd-*.lib' # FIXME
          for entry in glob.glob(os.path.join(libdir, globber)):
              (_, fname) = os.path.split(entry)
              base = fname.split('_', 1)[1]
              modname = base.split('-', 1)[0]
              self.lib_modules_mt[modname] = fname
  
      def detect_lib_modules_nix(self):
          if mesonlib.is_osx():
              libsuffix = 'dylib'
          else:
              libsuffix = 'so'
  
          globber = 'libboost_*.{}'.format(libsuffix)
          if 'BOOST_LIBRARYDIR' in os.environ:
              libdirs = [os.environ['BOOST_LIBRARYDIR']]
          elif self.boost_root is None:
              libdirs = mesonlib.get_library_dirs()
          else:
              libdirs = [os.path.join(self.boost_root, 'lib')]
          for libdir in libdirs:
              for entry in glob.glob(os.path.join(libdir, globber)):
                  lib = os.path.basename(entry)
                  name = lib.split('.')[0].split('_', 1)[-1]
                  # I'm not 100% sure what to do here. Some distros
                  # have modules such as thread only as -mt versions.
                  if entry.endswith('-mt.so'):
                      self.lib_modules_mt[name] = True
                  else:
                      self.lib_modules[name] = True
  
      def get_win_link_args(self):
          args = []
          if self.boost_root:
              args.append('-L' + self.libdir)
          for module in self.requested_modules:
              module = BoostDependency.name2lib.get(module, module)
              if module in self.lib_modules_mt:
                  args.append(self.lib_modules_mt[module])
          return args
  
      def get_link_args(self):
          if mesonlib.is_windows():
              return self.get_win_link_args()
          args = []
          if self.boost_root:
              args.append('-L' + os.path.join(self.boost_root, 'lib'))
          elif 'BOOST_LIBRARYDIR' in os.environ:
              args.append('-L' + os.environ['BOOST_LIBRARYDIR'])
          for module in self.requested_modules:
              module = BoostDependency.name2lib.get(module, module)
              libname = 'boost_' + module
              # The compiler's library detector is the most reliable so use that first.
              default_detect = self.cpp_compiler.find_library(libname, self.environment, [])
              if default_detect is not None:
                  if module == 'unit_testing_framework':
                      emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor')
                  else:
                      emon_args = None
                  args += default_detect
                  if emon_args is not None:
                      args += emon_args
              elif module in self.lib_modules or module in self.lib_modules_mt:
                  linkcmd = '-l' + libname
                  args.append(linkcmd)
                  # FIXME a hack, but Boost's testing framework has a lot of
                  # different options and it's hard to determine what to do
                  # without feedback from actual users. Update this
                  # as we get more bug reports.
                  if module == 'unit_testing_framework':
                      args.append('-lboost_test_exec_monitor')
              elif module + '-mt' in self.lib_modules_mt:
                  linkcmd = '-lboost_' + module + '-mt'
                  args.append(linkcmd)
                  if module == 'unit_testing_framework':
                      args.append('-lboost_test_exec_monitor-mt')
          return args
  
      def get_sources(self):
          return []
  
      def need_threads(self):
          return 'thread' in self.requested_modules
  
  class GTestDependency(Dependency):
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'gtest', kwargs)
          self.main = kwargs.get('main', False)
          self.name = 'gtest'
          self.libname = 'libgtest.so'
          self.libmain_name = 'libgtest_main.so'
          self.include_dir = '/usr/include'
          self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src']
          self.detect()
  
      def found(self):
          return self.is_found
  
      def detect(self):
          trial_dirs = mesonlib.get_library_dirs()
          glib_found = False
          gmain_found = False
          for d in trial_dirs:
              if os.path.isfile(os.path.join(d, self.libname)):
                  glib_found = True
              if os.path.isfile(os.path.join(d, self.libmain_name)):
                  gmain_found = True
          if glib_found and gmain_found:
              self.is_found = True
              self.compile_args = []
              self.link_args = ['-lgtest']
              if self.main:
                  self.link_args.append('-lgtest_main')
              self.sources = []
              mlog.log('Dependency GTest found:', mlog.green('YES'), '(prebuilt)')
          elif self.detect_srcdir():
              self.is_found = True
              self.compile_args = ['-I' + self.src_include_dir]
              self.link_args = []
              if self.main:
                  self.sources = [self.all_src, self.main_src]
              else:
                  self.sources = [self.all_src]
              mlog.log('Dependency GTest found:', mlog.green('YES
  
  [... the rest is omitted ...]
files: mesonbuild/dependencies.py
files: # Copyright 2013-2017 The Meson development team
  
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  
  #     http://www.apache.org/licenses/LICENSE-2.0
  
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # This file contains the detection logic for external
  # dependencies. Mostly just uses pkg-config but also contains
  # custom logic for packages that don't provide them.
  
  # Currently one file, should probably be split into a
  # package before this gets too big.
  
  import re
  import sys
  import os, stat, glob, shutil
  import subprocess
  import sysconfig
  from collections import OrderedDict
  from . mesonlib import MesonException, version_compare, version_compare_many, Popen_safe
  from . import mlog
  from . import mesonlib
  from .environment import detect_cpu_family, for_windows
  
  class DependencyException(MesonException):
      '''Exceptions raised while trying to find dependencies'''
  
  class Dependency:
      def __init__(self, type_name, kwargs):
          self.name = "null"
          self.is_found = False
          self.type_name = type_name
          method = kwargs.get('method', 'auto')
  
          # Set the detection method. If the method is set to auto, use any available method.
          # If method is set to a specific string, allow only that detection method.
          if method == "auto":
              self.methods = self.get_methods()
          elif method in self.get_methods():
              self.methods = [method]
          else:
              raise MesonException('Unsupported detection method: {}, allowed methods are {}'.format(method, mlog.format_list(["auto"] + self.get_methods())))
  
      def __repr__(self):
          s = '<{0} {1}: {2}>'
          return s.format(self.__class__.__name__, self.name, self.is_found)
  
      def get_compile_args(self):
          return []
  
      def get_link_args(self):
          return []
  
      def found(self):
          return self.is_found
  
      def get_sources(self):
          """Source files that need to be added to the target.
          As an example, gtest-all.cc when using GTest."""
          return []
  
      def get_methods(self):
          return ['auto']
  
      def get_name(self):
          return self.name
  
      def get_exe_args(self, compiler):
          return []
  
      def need_threads(self):
          return False
  
      def get_pkgconfig_variable(self, variable_name):
          raise MesonException('Tried to get a pkg-config variable from a non-pkgconfig dependency.')
  
  class InternalDependency(Dependency):
      def __init__(self, version, incdirs, compile_args, link_args, libraries, sources, ext_deps):
          super().__init__('internal', {})
          self.version = version
          self.include_directories = incdirs
          self.compile_args = compile_args
          self.link_args = link_args
          self.libraries = libraries
          self.sources = sources
          self.ext_deps = ext_deps
  
      def get_compile_args(self):
          return self.compile_args
  
      def get_link_args(self):
          return self.link_args
  
      def get_version(self):
          return self.version
  
  class PkgConfigDependency(Dependency):
      # The class's copy of the pkg-config path. Avoids having to search for it
      # multiple times in the same Meson invocation.
      class_pkgbin = None
  
      def __init__(self, name, environment, kwargs):
          Dependency.__init__(self, 'pkgconfig', kwargs)
          self.is_libtool = False
          self.required = kwargs.get('required', True)
          self.static = kwargs.get('static', False)
          self.silent = kwargs.get('silent', False)
          if not isinstance(self.static, bool):
              raise DependencyException('Static keyword must be boolean')
          # Store a copy of the pkg-config path on the object itself so it is
          # stored in the pickled coredata and recovered.
          self.pkgbin = None
          self.cargs = []
          self.libs = []
          if 'native' in kwargs and environment.is_cross_build():
              self.want_cross = not kwargs['native']
          else:
              self.want_cross = environment.is_cross_build()
          self.name = name
          self.modversion = 'none'
  
          # When finding dependencies for cross-compiling, we don't care about
          # the 'native' pkg-config
          if self.want_cross:
              if 'pkgconfig' not in environment.cross_info.config['binaries']:
                  if self.required:
                      raise DependencyException('Pkg-config binary missing from cross file')
              else:
                  pkgname = environment.cross_info.config['binaries']['pkgconfig']
                  potential_pkgbin = ExternalProgram(pkgname, silent=True)
                  if potential_pkgbin.found():
                      # FIXME, we should store all pkg-configs in ExternalPrograms.
                      # However that is too destabilizing a change to do just before release.
                      self.pkgbin = potential_pkgbin.get_command()[0]
                      PkgConfigDependency.class_pkgbin = self.pkgbin
                  else:
                      mlog.debug('Cross pkg-config %s not found.' % potential_pkgbin.name)
          # Only search for the native pkg-config the first time and
          # store the result in the class definition
          elif PkgConfigDependency.class_pkgbin is None:
              self.pkgbin = self.check_pkgconfig()
              PkgConfigDependency.class_pkgbin = self.pkgbin
          else:
              self.pkgbin = PkgConfigDependency.class_pkgbin
  
          self.is_found = False
          if not self.pkgbin:
              if self.required:
                  raise DependencyException('Pkg-config not found.')
              return
          if self.want_cross:
              self.type_string = 'Cross'
          else:
              self.type_string = 'Native'
  
          mlog.debug('Determining dependency {!r} with pkg-config executable '
                     '{!r}'.format(name, self.pkgbin))
          ret, self.modversion = self._call_pkgbin(['--modversion', name])
          if ret != 0:
              if self.required:
                  raise DependencyException('{} dependency {!r} not found'
                                            ''.format(self.type_string, name))
              return
          found_msg = [self.type_string + ' dependency', mlog.bold(name), 'found:']
          self.version_reqs = kwargs.get('version', None)
          if self.version_reqs is None:
              self.is_found = True
          else:
              if not isinstance(self.version_reqs, (str, list)):
                  raise DependencyException('Version argument must be string or list.')
              if isinstance(self.version_reqs, str):
                  self.version_reqs = [self.version_reqs]
              (self.is_found, not_found, found) = \
                  version_compare_many(self.modversion, self.version_reqs)
              if not self.is_found:
                  found_msg += [mlog.red('NO'),
                                'found {!r} but need:'.format(self.modversion),
                                ', '.join(["'{}'".format(e) for e in not_found])]
                  if found:
                      found_msg += ['; matched:',
                                    ', '.join(["'{}'".format(e) for e in found])]
                  if not self.silent:
                      mlog.log(*found_msg)
                  if self.required:
                      m = 'Invalid version of dependency, need {!r} {!r} found {!r}.'
                      raise DependencyException(m.format(name, not_found, self.modversion))
                  return
          found_msg += [mlog.green('YES'), self.modversion]
          # Fetch cargs to be used while using this dependency
          self._set_cargs()
          # Fetch the libraries and library paths needed for using this
          self._set_libs()
          # Print the found message only at the very end because fetching cflags
          # and libs can also fail if other needed pkg-config files aren't found.
          if not self.silent:
              mlog.log(*found_msg)
  
      def __repr__(self):
          s = '<{0} {1}: {2} {3}>'
          return s.format(self.__class__.__name__, self.name, self.is_found,
                          self.version_reqs)
  
      def _call_pkgbin(self, args):
          p, out = Popen_safe([self.pkgbin] + args, env=os.environ)[0:2]
          return p.returncode, out.strip()
  
      def _set_cargs(self):
          ret, out = self._call_pkgbin(['--cflags', self.name])
          if ret != 0:
              raise DependencyException('Could not generate cargs for %s:\n\n%s' %
                                        (self.name, out))
          self.cargs = out.split()
  
      def _set_libs(self):
          libcmd = [self.name, '--libs']
          if self.static:
              libcmd.append('--static')
          ret, out = self._call_pkgbin(libcmd)
          if ret != 0:
              raise DependencyException('Could not generate libs for %s:\n\n%s' %
                                        (self.name, out))
          self.libs = []
          for lib in out.split():
              if lib.endswith(".la"):
                  shared_libname = self.extract_libtool_shlib(lib)
                  shared_lib = os.path.join(os.path.dirname(lib), shared_libname)
                  if not os.path.exists(shared_lib):
                      shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname)
  
                  if not os.path.exists(shared_lib):
                      raise DependencyException('Got a libtools specific "%s" dependencies'
                                                'but we could not compute the actual shared'
                                                'library path' % lib)
                  lib = shared_lib
                  self.is_libtool = True
              self.libs.append(lib)
  
      def get_pkgconfig_variable(self, variable_name):
          ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name])
          variable = ''
          if ret != 0:
              if self.required:
                  raise DependencyException('%s dependency %s not found.' %
                                            (self.type_string, self.name))
          else:
              variable = out.strip()
          mlog.debug('Got pkgconfig variable %s : %s' % (variable_name, variable))
          return variable
  
      def get_modversion(self):
          return self.modversion
  
      def get_version(self):
          return self.modversion
  
      def get_compile_args(self):
          return self.cargs
  
      def get_link_args(self):
          return self.libs
  
      def get_methods(self):
          return ['pkg-config']
  
      def check_pkgconfig(self):
          evar = 'PKG_CONFIG'
          if evar in os.environ:
              pkgbin = os.environ[evar].strip()
          else:
              pkgbin = 'pkg-config'
          try:
              p, out = Popen_safe([pkgbin, '--version'])[0:2]
              if p.returncode != 0:
                  # Set to False instead of None to signify that we've already
                  # searched for it and not found it
                  pkgbin = False
          except (FileNotFoundError, PermissionError):
              pkgbin = False
          if pkgbin and not os.path.isabs(pkgbin) and shutil.which(pkgbin):
              # Sometimes shutil.which fails where Popen succeeds, so
              # only find the abs path if it can be found by shutil.which
              pkgbin = shutil.which(pkgbin)
          if not self.silent:
              if pkgbin:
                  mlog.log('Found pkg-config:', mlog.bold(pkgbin),
                           '(%s)' % out.strip())
              else:
                  mlog.log('Found Pkg-config:', mlog.red('NO'))
          return pkgbin
  
      def found(self):
          return self.is_found
  
      def extract_field(self, la_file, fieldname):
          with open(la_file) as f:
              for line in f:
                  arr = line.strip().split('=')
                  if arr[0] == fieldname:
                      return arr[1][1:-1]
          return None
  
      def extract_dlname_field(self, la_file):
          return self.extract_field(la_file, 'dlname')
  
      def extract_libdir_field(self, la_file):
          return self.extract_field(la_file, 'libdir')
  
      def extract_libtool_shlib(self, la_file):
          '''
          Returns the path to the shared library
          corresponding to this .la file
          '''
          dlname = self.extract_dlname_field(la_file)
          if dlname is None:
              return None
  
          # Darwin uses absolute paths where possible; since the libtool files never
          # contain absolute paths, use the libdir field
          if mesonlib.is_osx():
              dlbasename = os.path.basename(dlname)
              libdir = self.extract_libdir_field(la_file)
              if libdir is None:
                  return dlbasename
              return os.path.join(libdir, dlbasename)
          # From the comments in extract_libtool(), older libtools had
          # a path rather than the raw dlname
          return os.path.basename(dlname)
  
  class WxDependency(Dependency):
      wx_found = None
  
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'wx', kwargs)
          self.is_found = False
          self.modversion = 'none'
          if WxDependency.wx_found is None:
              self.check_wxconfig()
          if not WxDependency.wx_found:
              mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency")
              return
  
          p, out = Popen_safe([self.wxc, '--version'])[0:2]
          if p.returncode != 0:
              mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
              self.cargs = []
              self.libs = []
          else:
              self.modversion = out.strip()
              version_req = kwargs.get('version', None)
              if version_req is not None:
                  if not version_compare(self.modversion, version_req, strict=True):
                      mlog.log('Wxwidgets version %s does not fullfill requirement %s' %
                               (self.modversion, version_req))
                      return
              mlog.log('Dependency wxwidgets found:', mlog.green('YES'))
              self.is_found = True
              self.requested_modules = self.get_requested(kwargs)
              # wx-config seems to have a cflags as well but since it requires C++,
              # this should be good, at least for now.
              p, out = Popen_safe([self.wxc, '--cxxflags'])[0:2]
              if p.returncode != 0:
                  raise DependencyException('Could not generate cargs for wxwidgets.')
              self.cargs = out.split()
  
              p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2]
              if p.returncode != 0:
                  raise DependencyException('Could not generate libs for wxwidgets.')
              self.libs = out.split()
  
      def get_requested(self, kwargs):
          modules = 'modules'
          if modules not in kwargs:
              return []
          candidates = kwargs[modules]
          if isinstance(candidates, str):
              return [candidates]
          for c in candidates:
              if not isinstance(c, str):
                  raise DependencyException('wxwidgets module argument is not a string.')
          return candidates
  
      def get_modversion(self):
          return self.modversion
  
      def get_version(self):
          return self.modversion
  
      def get_compile_args(self):
          return self.cargs
  
      def get_link_args(self):
          return self.libs
  
      def check_wxconfig(self):
          for wxc in ['wx-config-3.0', 'wx-config']:
              try:
                  p, out = Popen_safe([wxc, '--version'])[0:2]
                  if p.returncode == 0:
                      mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
                               '(%s)' % out.strip())
                      self.wxc = wxc
                      WxDependency.wx_found = True
                      return
              except (FileNotFoundError, PermissionError):
                  pass
          WxDependency.wxconfig_found = False
          mlog.log('Found wx-config:', mlog.red('NO'))
  
      def found(self):
          return self.is_found
  
  class ExternalProgram:
      windows_exts = ('exe', 'msc', 'com', 'bat')
  
      def __init__(self, name, command=None, silent=False, search_dir=None):
          self.name = name
          if command is not None:
              if not isinstance(command, list):
                  self.command = [command]
              else:
                  self.command = command
          else:
              self.command = self._search(name, search_dir)
          if not silent:
              if self.found():
                  mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
                           '(%s)' % ' '.join(self.command))
              else:
                  mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
  
      def __repr__(self):
          r = '<{} {!r} -> {!r}>'
          return r.format(self.__class__.__name__, self.name, self.command)
  
      @staticmethod
      def _shebang_to_cmd(script):
          """
          Windows does not understand shebangs, so we check if the file has a
          shebang and manually parse it to figure out the interpreter to use
          """
          try:
              with open(script) as f:
                  first_line = f.readline().strip()
              if first_line.startswith('#!'):
                  commands = first_line[2:].split('#')[0].strip().split()
                  if mesonlib.is_windows():
                      # Windows does not have UNIX paths so remove them,
                      # but don't remove Windows paths
                      if commands[0].startswith('/'):
                          commands[0] = commands[0].split('/')[-1]
                      if len(commands) > 0 and commands[0] == 'env':
                          commands = commands[1:]
                      # Windows does not ship python3.exe, but we know the path to it
                      if len(commands) > 0 and commands[0] == 'python3':
                          commands[0] = sys.executable
                  return commands + [script]
          except Exception:
              pass
          return False
  
      def _is_executable(self, path):
          suffix = os.path.splitext(path)[-1].lower()[1:]
          if mesonlib.is_windows():
              if suffix in self.windows_exts:
                  return True
          elif os.access(path, os.X_OK):
              return not os.path.isdir(path)
          return False
  
      def _search_dir(self, name, search_dir):
          if search_dir is None:
              return False
          trial = os.path.join(search_dir, name)
          if os.path.exists(trial):
              if self._is_executable(trial):
                  return [trial]
          else:
              for ext in self.windows_exts:
                  trial_ext = '{}.{}'.format(trial, ext)
                  if os.path.exists(trial_ext):
                      return [trial_ext]
              return False
          # Now getting desperate. Maybe it is a script file that is a) not chmodded
          # executable or b) we are on windows so they can't be directly executed.
          return self._shebang_to_cmd(trial)
  
      def _search(self, name, search_dir):
          '''
          Search in the specified dir for the specified executable by name
          and if not found search in PATH
          '''
          commands = self._search_dir(name, search_dir)
          if commands:
              return commands
          # Do a standard search in PATH
          command = shutil.which(name)
          if not mesonlib.is_windows():
              # On UNIX-like platforms, the standard PATH search is enough
              return [command]
          # HERE BEGINS THE TERROR OF WINDOWS
          if command:
              # On Windows, even if the PATH search returned a full path, we can't be
              # sure that it can be run directly if it's not a native executable.
              # For instance, interpreted scripts sometimes need to be run explicitly
              # with an interpreter if the file association is not done properly.
              name_ext = os.path.splitext(command)[1]
              if name_ext[1:].lower() in self.windows_exts:
                  # Good, it can be directly executed
                  return [command]
              # Try to extract the interpreter from the shebang
              commands = self._shebang_to_cmd(command)
              if commands:
                  return commands
          else:
              # Maybe the name is an absolute path to a native Windows
              # executable, but without the extension. This is technically wrong,
              # but many people do it because it works in the MinGW shell.
              if os.path.isabs(name):
                  for ext in self.windows_exts:
                      command = '{}.{}'.format(name, ext)
                      if os.path.exists(command):
                          return [command]
              # On Windows, interpreted scripts must have an extension otherwise they
              # cannot be found by a standard PATH search. So we do a custom search
              # where we manually search for a script with a shebang in PATH.
              search_dirs = os.environ.get('PATH', '').split(';')
              for search_dir in search_dirs:
                  commands = self._search_dir(name, search_dir)
                  if commands:
                      return commands
          return [None]
  
      def found(self):
          return self.command[0] is not None
  
      def get_command(self):
          return self.command[:]
  
      def get_path(self):
          # Assume that the last element is the full path to the script
          # If it's not a script, this will be an array of length 1
          if self.found():
              return self.command[-1]
          return None
  
      def get_name(self):
          return self.name
  
  class ExternalLibrary(Dependency):
      # TODO: Add `lang` to the parent Dependency object so that dependencies can
      # be expressed for languages other than C-like
      def __init__(self, name, link_args=None, language=None, silent=False):
          super().__init__('external', {})
          self.name = name
          self.is_found = False
          self.link_args = []
          self.lang_args = []
          if link_args:
              self.is_found = True
              if not isinstance(link_args, list):
                  link_args = [link_args]
              if language:
                  self.lang_args = {language: link_args}
              else:
                  self.link_args = link_args
          if not silent:
              if self.is_found:
                  mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'))
              else:
                  mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
  
      def found(self):
          return self.is_found
  
      def get_name(self):
          return self.name
  
      def get_link_args(self):
          return self.link_args
  
      def get_lang_args(self, lang):
          if lang in self.lang_args:
              return self.lang_args[lang]
          return []
  
  class BoostDependency(Dependency):
      # Some boost libraries have different names for
      # their sources and libraries. This dict maps
      # between the two.
      name2lib = {'test': 'unit_test_framework'}
  
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'boost', kwargs)
          self.name = 'boost'
          self.environment = environment
          self.libdir = ''
          if 'native' in kwargs and environment.is_cross_build():
              self.want_cross = not kwargs['native']
          else:
              self.want_cross = environment.is_cross_build()
          try:
              self.boost_root = os.environ['BOOST_ROOT']
              if not os.path.isabs(self.boost_root):
                  raise DependencyException('BOOST_ROOT must be an absolute path.')
          except KeyError:
              self.boost_root = None
          if self.boost_root is None:
              if self.want_cross:
                  if 'BOOST_INCLUDEDIR' in os.environ:
                      self.incdir = os.environ['BOOST_INCLUDEDIR']
                  else:
                      raise DependencyException('BOOST_ROOT or BOOST_INCLUDEDIR is needed while cross-compiling')
              if mesonlib.is_windows():
                  self.boost_root = self.detect_win_root()
                  self.incdir = self.boost_root
              else:
                  if 'BOOST_INCLUDEDIR' in os.environ:
                      self.incdir = os.environ['BOOST_INCLUDEDIR']
                  else:
                      self.incdir = '/usr/include'
          else:
              self.incdir = os.path.join(self.boost_root, 'include')
          self.boost_inc_subdir = os.path.join(self.incdir, 'boost')
          mlog.debug('Boost library root dir is', self.boost_root)
          self.src_modules = {}
          self.lib_modules = {}
          self.lib_modules_mt = {}
          self.detect_version()
          self.requested_modules = self.get_requested(kwargs)
          module_str = ', '.join(self.requested_modules)
          if self.version is not None:
              self.detect_src_modules()
              self.detect_lib_modules()
              self.validate_requested()
              if self.boost_root is not None:
                  info = self.version + ', ' + self.boost_root
              else:
                  info = self.version
              mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info)
          else:
              mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO'))
          if 'cpp' not in self.environment.coredata.compilers:
              raise DependencyException('Tried to use Boost but a C++ compiler is not defined.')
          self.cpp_compiler = self.environment.coredata.compilers['cpp']
  
      def detect_win_root(self):
          globtext = 'c:\\local\\boost_*'
          files = glob.glob(globtext)
          if len(files) > 0:
              return files[0]
          return 'C:\\'
  
      def get_compile_args(self):
          args = []
          if self.boost_root is not None:
              if mesonlib.is_windows():
                  args.append('-I' + self.boost_root)
              else:
                  args.append('-I' + os.path.join(self.boost_root, 'include'))
          else:
              args.append('-I' + self.incdir)
          return args
  
      def get_requested(self, kwargs):
          candidates = kwargs.get('modules', [])
          if isinstance(candidates, str):
              return [candidates]
          for c in candidates:
              if not isinstance(c, str):
                  raise DependencyException('Boost module argument is not a string.')
          return candidates
  
      def validate_requested(self):
          for m in self.requested_modules:
              if m not in self.src_modules:
                  raise DependencyException('Requested Boost module "%s" not found.' % m)
  
      def found(self):
          return self.version is not None
  
      def get_version(self):
          return self.version
  
      def detect_version(self):
          try:
              ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp'))
          except FileNotFoundError:
              self.version = None
              return
          with ifile:
              for line in ifile:
                  if line.startswith("#define") and 'BOOST_LIB_VERSION' in line:
                      ver = line.split()[-1]
                      ver = ver[1:-1]
                      self.version = ver.replace('_', '.')
                      return
          self.version = None
  
      def detect_src_modules(self):
          for entry in os.listdir(self.boost_inc_subdir):
              entry = os.path.join(self.boost_inc_subdir, entry)
              if stat.S_ISDIR(os.stat(entry).st_mode):
                  self.src_modules[os.path.split(entry)[-1]] = True
  
      def detect_lib_modules(self):
          if mesonlib.is_windows():
              return self.detect_lib_modules_win()
          return self.detect_lib_modules_nix()
  
      def detect_lib_modules_win(self):
          arch = detect_cpu_family(self.environment.coredata.compilers)
          # Guess the libdir
          if arch == 'x86':
              gl = 'lib32*'
          elif arch == 'x86_64':
              gl = 'lib64*'
          else:
              # Does anyone do Boost cross-compiling to other archs on Windows?
              gl = None
          # See if the libdir is valid
          if gl:
              libdir = glob.glob(os.path.join(self.boost_root, gl))
          else:
              libdir = []
          # Can't find libdir, bail
          if len(libdir) == 0:
              return
          libdir = libdir[0]
          self.libdir = libdir
          globber = 'boost_*-gd-*.lib' # FIXME
          for entry in glob.glob(os.path.join(libdir, globber)):
              (_, fname) = os.path.split(entry)
              base = fname.split('_', 1)[1]
              modname = base.split('-', 1)[0]
              self.lib_modules_mt[modname] = fname
  
      def detect_lib_modules_nix(self):
          if mesonlib.is_osx():
              libsuffix = 'dylib'
          else:
              libsuffix = 'so'
  
          globber = 'libboost_*.{}'.format(libsuffix)
          if 'BOOST_LIBRARYDIR' in os.environ:
              libdirs = [os.environ['BOOST_LIBRARYDIR']]
          elif self.boost_root is None:
              libdirs = mesonlib.get_library_dirs()
          else:
              libdirs = [os.path.join(self.boost_root, 'lib')]
          for libdir in libdirs:
              for entry in glob.glob(os.path.join(libdir, globber)):
                  lib = os.path.basename(entry)
                  name = lib.split('.')[0].split('_', 1)[-1]
                  # I'm not 100% sure what to do here. Some distros
                  # have modules such as thread only as -mt versions.
                  if entry.endswith('-mt.so'):
                      self.lib_modules_mt[name] = True
                  else:
                      self.lib_modules[name] = True
  
      def get_win_link_args(self):
          args = []
          if self.boost_root:
              args.append('-L' + self.libdir)
          for module in self.requested_modules:
              module = BoostDependency.name2lib.get(module, module)
              if module in self.lib_modules_mt:
                  args.append(self.lib_modules_mt[module])
          return args
  
      def get_link_args(self):
          if mesonlib.is_windows():
              return self.get_win_link_args()
          args = []
          if self.boost_root:
              args.append('-L' + os.path.join(self.boost_root, 'lib'))
          elif 'BOOST_LIBRARYDIR' in os.environ:
              args.append('-L' + os.environ['BOOST_LIBRARYDIR'])
          for module in self.requested_modules:
              module = BoostDependency.name2lib.get(module, module)
              libname = 'boost_' + module
              # The compiler's library detector is the most reliable so use that first.
              default_detect = self.cpp_compiler.find_library(libname, self.environment, [])
              if default_detect is not None:
                  if module == 'unit_testing_framework':
                      emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor')
                  else:
                      emon_args = None
                  args += default_detect
                  if emon_args is not None:
                      args += emon_args
              elif module in self.lib_modules or module in self.lib_modules_mt:
                  linkcmd = '-l' + libname
                  args.append(linkcmd)
                  # FIXME a hack, but Boost's testing framework has a lot of
                  # different options and it's hard to determine what to do
                  # without feedback from actual users. Update this
                  # as we get more bug reports.
                  if module == 'unit_testing_framework':
                      args.append('-lboost_test_exec_monitor')
              elif module + '-mt' in self.lib_modules_mt:
                  linkcmd = '-lboost_' + module + '-mt'
                  args.append(linkcmd)
                  if module == 'unit_testing_framework':
                      args.append('-lboost_test_exec_monitor-mt')
          return args
  
      def get_sources(self):
          return []
  
      def need_threads(self):
          return 'thread' in self.requested_modules
  
  class GTestDependency(Dependency):
      def __init__(self, environment, kwargs):
          Dependency.__init__(self, 'gtest', kwargs)
          self.main = kwargs.get('main', False)
          self.name = 'gtest'
          self.libname = 'libgtest.so'
          self.libmain_name = 'libgtest_main.so'
          self.include_dir = '/usr/include'
          self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src']
          self.detect()
  
      def found(self):
          return self.is_found
  
      def detect(self):
          trial_dirs = mesonlib.get_library_dirs()
          glib_found = False
          gmain_found = False
          for d in trial_dirs:
              if os.path.isfile(os.path.join(d, self.libname)):
                  glib_found = True
              if os.path.isfile(os.path.join(d, self.libmain_name)):
                  gmain_found = True
          if glib_found and gmain_found:
              self.is_found = True
              self.compile_args = []
              self.link_args = ['-lgtest']
              if self.main:
                  self.link_args.append('-lgtest_main')
              self.sources = []
              mlog.log('Dependency GTest found:', mlog.green('YES'), '(prebuilt)')
          elif self.detect_srcdir():
              self.is_found = True
              self.compile_args = ['-I' + self.src_include_dir]
              self.link_args = []
              if self.main:
                  self.sources = [self.all_src, self.main_src]
              else:
                  self.sources = [self.all_src]
              mlog.log('Dependency GTest found:', mlog.green('YES'), '(building self)')
          else:
              mlog.log('Dependency GTest found:', mlog.red('NO'))
              self.is_found = False
          return self.is_found
  
      def detect_srcdir(self):
          for s in self.src_dirs:
              if os.path.exists(s):
                  self.src_dir = s
                  self.all_src = mesonlib.File.from_absolute_file(
                      os.path.join(self.src_dir, 'gtest-all.cc'))
                  self.main_src = mesonlib.File.from_absolute_file(
                      os.path.join(self.src_dir, 'gtest_main.cc'))
                  self.src_include_dir = os.path.normpath(os.path.join(self.src_dir, '..'))
                  return True
          return False
  
      def get_compile_args(self):
          arr = []
          if
  
  [... the rest is omitted ...]
files: mesonbuild/dependencies.py