| # 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 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 += ['-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 | |
| def log_info(self) -> str: | |
| return self.framework_path or '' | |
| def log_tried(self) -> str: | |
| return 'framework' |