text: # Copyright 2013-2021 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 .base import ExternalDependency, DependencyException, DependencyMethods
  from ..mesonlib import MesonException, Version, stringlistify
  from .. import mlog
  from pathlib import Path
  import typing as T
  
  if T.TYPE_CHECKING:
      from ..environment import Environment
  
  class ExtraFrameworkDependency(ExternalDependency):
      system_framework_paths: T.Optional[T.List[str]] = None
  
      def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
          paths = stringlistify(kwargs.get('paths', []))
          super().__init__('extraframeworks', env, kwargs, language=language)
          self.name = name
          # Full path to framework directory
          self.framework_path: T.Optional[str] = None
          if not self.clib_compiler:
              raise DependencyException('No C-like compilers are available')
          if self.system_framework_paths is None:
              try:
                  self.system_framework_paths = self.clib_compiler.find_framework_paths(self.env)
              except MesonException as e:
                  if 'non-clang' in str(e):
                      # Apple frameworks can only be found (and used) with the
                      # system compiler. It is not available so bail immediately.
                      self.is_found = False
                      return
                  raise
          self.detect(name, paths)
  
      def detect(self, name: str, paths: T.List[str]) -> None:
          if not paths:
              paths = self.system_framework_paths
          for p in paths:
              mlog.debug(f'Looking for framework {name} in {p}')
              # We need to know the exact framework path because it's used by the
              # Qt5 dependency class, and for setting the include path. We also
              # want to avoid searching in an invalid framework path which wastes
              # time and can cause a false positive.
              framework_path = self._get_framework_path(p, name)
              if framework_path is None:
                  continue
              # We want to prefer the specified paths (in order) over the system
              # paths since these are "extra" frameworks.
              # For example, Python2's framework is in /System/Library/Frameworks and
              # Python3's framework is in /Library/Frameworks, but both are called
              # Python.framework. We need to know for sure that the framework was
              # found in the path we expect.
              allow_system = p in self.system_framework_paths
              args = self.clib_compiler.find_framework(name, self.env, [p], allow_system)
              if args is None:
                  continue
              self.link_args = args
              self.framework_path = framework_path.as_posix()
              self.compile_args = ['-F' + self.framework_path]
              # We need to also add -I includes to the framework because all
              # cross-platform projects such as OpenGL, Python, Qt, GStreamer,
              # etc do not use "framework includes":
              # https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Tasks/IncludingFrameworks.html
              incdir = self._get_framework_include_path(framework_path)
              if incdir:
                  self.compile_args += ['-I' + incdir]
              self.is_found = True
              return
  
      def _get_framework_path(self, path: str, name: str) -> T.Optional[Path]:
          p = Path(path)
          lname = name.lower()
          for d in p.glob('*.framework/'):
              if lname == d.name.rsplit('.', 1)[0].lower():
                  return d
          return None
  
      def _get_framework_latest_version(self, path: Path) -> str:
          versions = []
          for each in path.glob('Versions/*'):
              # macOS filesystems are usually case-insensitive
              if each.name.lower() == 'current':
                  continue
              versions.append(Version(each.name))
          if len(versions) == 0:
              # most system frameworks do not have a 'Versions' directory
              return 'Headers'
          return 'Versions/{}/Headers'.format(sorted(versions)[-1]._s)
  
      def _get_framework_include_path(self, path: Path) -> T.Optional[str]:
          # According to the spec, 'Headers' must always be a symlink to the
          # Headers directory inside the currently-selected version of the
          # framework, but sometimes frameworks are broken. Look in 'Versions'
          # for the currently-selected version or pick the latest one.
          trials = ('Headers', 'Versions/Current/Headers',
                    self._get_framework_latest_version(path))
          for each in trials:
              trial = path / each
              if trial.is_dir():
                  return trial.as_posix()
          return None
  
      @staticmethod
      def get_methods() -> T.List[DependencyMethods]:
          return [DependencyMethods.EXTRAFRAMEWORK]
  
      def log_info(self) -> str:
          return self.framework_path or ''
  
      def log_tried(self) -> str:
          return 'framework'
  