# 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 __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 += ['-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 '' @staticmethod def log_tried() -> str: return 'framework'