| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2013-2021 The Meson development team |
| |
| from __future__ import annotations |
| |
| from .base import DependencyTypeName, ExternalDependency, DependencyException |
| 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__(DependencyTypeName('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 += ['-idirafter' + 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: T.List[Version] = [] |
| 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 |
| |
| def log_info(self) -> str: |
| return self.framework_path or '' |
| |
| @staticmethod |
| def log_tried() -> str: |
| return 'framework' |