diff options
author | Nirbheek Chauhan <nirbheek@centricular.com> | 2019-01-30 15:28:02 +0530 |
---|---|---|
committer | Nirbheek Chauhan <nirbheek.chauhan@gmail.com> | 2019-02-01 00:14:09 +0530 |
commit | c0166355ceef5168b2f7b3c6cbace32e8dbafbb4 (patch) | |
tree | 5ad1f9694597e5d9536d098dbf211537a48b917c /mesonbuild | |
parent | 8481971ff2459ed34e2acb4ce4bb20d1efe6d215 (diff) | |
download | meson-c0166355ceef5168b2f7b3c6cbace32e8dbafbb4.zip meson-c0166355ceef5168b2f7b3c6cbace32e8dbafbb4.tar.gz meson-c0166355ceef5168b2f7b3c6cbace32e8dbafbb4.tar.bz2 |
Rewrite appleframework and extraframework dependency classes
Instead of only doing a naive filesystem search, also run the linker
so that it can tell us whether the -F path specified actually contains
the framework we're looking for.
Unfortunately, `extraframework` searching is still not 100% correct in
the case when since we want to search in either /Library/Frameworks or
in /System/Library/Frameworks but not in both. The -Z flag disables
searching in those prefixes and would in theory allow this, but then
you cannot force the linker to look in those by manually adding -F
args, so that doesn't work.
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/compilers/c.py | 61 | ||||
-rw-r--r-- | mesonbuild/dependencies/base.py | 116 | ||||
-rw-r--r-- | mesonbuild/dependencies/misc.py | 2 | ||||
-rw-r--r-- | mesonbuild/dependencies/platform.py | 16 | ||||
-rw-r--r-- | mesonbuild/dependencies/ui.py | 8 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 3 |
6 files changed, 169 insertions, 37 deletions
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index c0cd0bc..0a90c8c 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -59,6 +59,7 @@ class CCompiler(Compiler): library_dirs_cache = {} program_dirs_cache = {} find_library_cache = {} + find_framework_cache = {} internal_libs = gnu_compiler_internal_libs @staticmethod @@ -1052,6 +1053,66 @@ class CCompiler(Compiler): code = 'int main(int argc, char **argv) { return 0; }' return self.find_library_impl(libname, env, extra_dirs, code, libtype) + def find_framework_paths(self, env): + ''' + These are usually /Library/Frameworks and /System/Library/Frameworks, + unless you select a particular macOS SDK with the -isysroot flag. + You can also add to this by setting -F in CFLAGS. + ''' + if self.id != 'clang': + raise MesonException('Cannot find framework path with non-clang compiler') + # Construct the compiler command-line + commands = self.get_exelist() + ['-v', '-E', '-'] + commands += self.get_always_args() + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + commands += env.coredata.get_external_args(self.language) + mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + _, _, stde = Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) + paths = [] + for line in stde.split('\n'): + if '(framework directory)' not in line: + continue + # line is of the form: + # ` /path/to/framework (framework directory)` + paths.append(line[:-21].strip()) + return paths + + def find_framework_real(self, name, env, extra_dirs, allow_system): + code = 'int main(int argc, char **argv) { return 0; }' + link_args = [] + for d in extra_dirs: + link_args += ['-F' + d] + # We can pass -Z to disable searching in the system frameworks, but + # then we must also pass -L/usr/lib to pick up libSystem.dylib + extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] + link_args += ['-framework', name] + if self.links(code, env, extra_args=(extra_args + link_args)): + return link_args + + def find_framework_impl(self, name, env, extra_dirs, allow_system): + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), name, tuple(extra_dirs), allow_system) + if key in self.find_framework_cache: + value = self.find_framework_cache[key] + else: + value = self.find_framework_real(name, env, extra_dirs, allow_system) + self.find_framework_cache[key] = value + if value is None: + return None + return value[:] + + def find_framework(self, name, env, extra_dirs, allow_system=True): + ''' + Finds the framework with the specified name, and returns link args for + the same or returns None when the framework is not found. + ''' + if self.id != 'clang': + raise MesonException('Cannot find frameworks with non-clang compiler') + return self.find_framework_impl(name, env, extra_dirs, allow_system) + def thread_flags(self, env): if for_haiku(self.is_cross, env) or for_darwin(self.is_cross, env): return [] diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index d56b825..d881f0e 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -28,7 +28,7 @@ import platform import itertools import ctypes from enum import Enum -from pathlib import PurePath +from pathlib import Path, PurePath from .. import mlog from .. import mesonlib @@ -36,6 +36,7 @@ from ..compilers import clib_langs from ..environment import BinaryTable from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify +from ..mesonlib import Version # These must be defined in this file to avoid cyclical references. packages = {} @@ -1983,40 +1984,91 @@ class ExternalLibrary(ExternalDependency): class ExtraFrameworkDependency(ExternalDependency): - def __init__(self, name, required, path, env, lang, kwargs): + system_framework_paths = None + + def __init__(self, name, required, paths, env, lang, kwargs): super().__init__('extraframeworks', env, lang, kwargs) self.name = name self.required = required - self.detect(name, path) - if self.found(): - self.compile_args = ['-I' + os.path.join(self.path, self.name, 'Headers')] - self.link_args = ['-F' + self.path, '-framework', self.name.split('.')[0]] - - def detect(self, name, path): - # should use the compiler to look for frameworks, rather than peering at - # the filesystem, so we can also find them when cross-compiling - if self.want_cross: + # Full path to framework directory + self.framework_path = None + if not self.clib_compiler: + raise DependencyException('No C-like compilers are available') + if self.system_framework_paths is None: + self.system_framework_paths = self.clib_compiler.find_framework_paths(self.env) + self.detect(name, paths) + + def detect(self, name, paths): + if not paths: + paths = self.system_framework_paths + for p in paths: + mlog.debug('Looking for framework {} in {}'.format(name, 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, name): + p = Path(path) lname = name.lower() - if path is None: - paths = ['/System/Library/Frameworks', '/Library/Frameworks'] - else: - paths = [path] - for p in paths: - for d in os.listdir(p): - fullpath = os.path.join(p, d) - if lname != d.rsplit('.', 1)[0].lower(): - continue - if not stat.S_ISDIR(os.stat(fullpath).st_mode): - continue - self.path = p - self.name = d - self.is_found = True - return + 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): + versions = [] + for each in path.glob('Versions/*'): + # macOS filesystems are usually case-insensitive + if each.name.lower() == 'current': + continue + versions.append(Version(each.name)) + return 'Versions/{}/Headers'.format(sorted(versions)[-1]._s) + + def _get_framework_include_path(self, path): + # 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(): + return [DependencyMethods.EXTRAFRAMEWORK] def log_info(self): - return os.path.join(self.path, self.name) + return self.framework_path def log_tried(self): return 'framework' @@ -2127,7 +2179,7 @@ def find_external_dependency(name, env, kwargs): # if an exception occurred with the first detection method, re-raise it # (on the grounds that it came from the preferred dependency detection # method) - if pkg_exc[0]: + if pkg_exc and pkg_exc[0]: raise pkg_exc[0] # we have a list of failed ExternalDependency objects, so we can report @@ -2172,6 +2224,14 @@ def _build_external_dependency_list(name, env, kwargs): candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) return candidates + # If it's explicitly requested, use the Extraframework detection method (only) + if 'extraframework' == kwargs.get('method', ''): + # On OSX, also try framework dependency detector + if mesonlib.is_osx(): + candidates.append(functools.partial(ExtraFrameworkDependency, name, + False, None, env, None, kwargs)) + return candidates + # Otherwise, just use the pkgconfig and cmake dependency detector if 'auto' == kwargs.get('method', 'auto'): candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index e6f52a5..4a57952 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -307,7 +307,7 @@ class Python3Dependency(ExternalDependency): # There is a python in /System/Library/Frameworks, but that's # python 2, Python 3 will always be in /Library candidates.append(functools.partial( - ExtraFrameworkDependency, 'python', False, '/Library/Frameworks', + ExtraFrameworkDependency, 'Python', False, ['/Library/Frameworks'], environment, kwargs.get('language', None), kwargs)) return candidates diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py index c78ebed..20d3bd6 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -29,11 +29,19 @@ class AppleFrameworks(ExternalDependency): if not modules: raise DependencyException("AppleFrameworks dependency requires at least one module.") self.frameworks = modules - # FIXME: Use self.clib_compiler to check if the frameworks are available + if not self.clib_compiler: + raise DependencyException('No C-like compilers are available, cannot find the framework') + self.is_found = True for f in self.frameworks: - self.link_args += ['-framework', f] - - self.is_found = mesonlib.for_darwin(self.want_cross, self.env) + args = self.clib_compiler.find_framework(f, env, []) + if args is not None: + # No compile args are needed for system frameworks + self.link_args = args + else: + self.is_found = False + + def log_info(self): + return ', '.join(self.frameworks) def log_tried(self): return 'framework' diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index dbdba9b..0585be9 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -177,13 +177,13 @@ def _qt_get_private_includes(mod_inc_dir, module, mod_version): os.path.join(private_dir, 'Qt' + module)) class QtExtraFrameworkDependency(ExtraFrameworkDependency): - def __init__(self, name, required, path, env, lang, kwargs): - super().__init__(name, required, path, env, lang, kwargs) + def __init__(self, name, required, paths, env, lang, kwargs): + super().__init__(name, required, paths, env, lang, kwargs) self.mod_name = name[2:] def get_compile_args(self, with_private_headers=False, qt_version="0"): if self.found(): - mod_inc_dir = os.path.join(self.path, self.name, 'Headers') + mod_inc_dir = os.path.join(self.framework_path, 'Headers') args = ['-I' + mod_inc_dir] if with_private_headers: args += ['-I' + dirname for dirname in _qt_get_private_includes(mod_inc_dir, self.mod_name, qt_version)] @@ -442,7 +442,7 @@ class QtBaseDependency(ExternalDependency): for m in modules: fname = 'Qt' + m - fwdep = QtExtraFrameworkDependency(fname, False, libdir, self.env, + fwdep = QtExtraFrameworkDependency(fname, False, [libdir], self.env, self.language, fw_kwargs) self.compile_args.append('-F' + libdir) if fwdep.found(): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 8454d79..5529ce4 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -496,6 +496,9 @@ class Version: def __str__(self): return '%s (V=%s)' % (self._s, str(self._v)) + def __repr__(self): + return '<Version: {}>'.format(self._s) + def __lt__(self, other): return self.__cmp__(other) == -1 |