aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/compilers/c.py61
-rw-r--r--mesonbuild/dependencies/base.py116
-rw-r--r--mesonbuild/dependencies/misc.py2
-rw-r--r--mesonbuild/dependencies/platform.py16
-rw-r--r--mesonbuild/dependencies/ui.py8
-rw-r--r--mesonbuild/mesonlib.py3
-rwxr-xr-xrun_tests.py1
-rwxr-xr-xrun_unittests.py8
-rw-r--r--test cases/osx/5 extra frameworks/installed_files.txt2
-rw-r--r--test cases/osx/5 extra frameworks/meson.build13
-rw-r--r--test cases/osx/5 extra frameworks/prog.c3
-rw-r--r--test cases/osx/5 extra frameworks/stat.c1
12 files changed, 197 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
diff --git a/run_tests.py b/run_tests.py
index 2805375..943e16f 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -217,6 +217,7 @@ def clear_meson_configure_class_caches():
mesonbuild.compilers.CCompiler.library_dirs_cache = {}
mesonbuild.compilers.CCompiler.program_dirs_cache = {}
mesonbuild.compilers.CCompiler.find_library_cache = {}
+ mesonbuild.compilers.CCompiler.find_framework_cache = {}
mesonbuild.dependencies.PkgConfigDependency.pkgbin_cache = {}
mesonbuild.dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None, None)
diff --git a/run_unittests.py b/run_unittests.py
index f1bf5c6..b8b3192 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -3519,6 +3519,14 @@ class FailureTests(BasePlatformTests):
self.assertMesonRaises("dependency('appleframeworks')",
"requires at least one module")
+ def test_extraframework_dependency_method(self):
+ code = "dependency('python', method : 'extraframework')"
+ if not is_osx():
+ self.assertMesonRaises(code, self.dnf)
+ else:
+ # Python2 framework is always available on macOS
+ self.assertMesonOutputs(code, '[Dd]ependency.*python.*found.*YES')
+
def test_sdl2_notfound_dependency(self):
# Want to test failure, so skip if available
if shutil.which('sdl2-config'):
diff --git a/test cases/osx/5 extra frameworks/installed_files.txt b/test cases/osx/5 extra frameworks/installed_files.txt
new file mode 100644
index 0000000..2c6bd93
--- /dev/null
+++ b/test cases/osx/5 extra frameworks/installed_files.txt
@@ -0,0 +1,2 @@
+usr/bin/prog
+usr/lib/libstat.a
diff --git a/test cases/osx/5 extra frameworks/meson.build b/test cases/osx/5 extra frameworks/meson.build
new file mode 100644
index 0000000..cb4847e
--- /dev/null
+++ b/test cases/osx/5 extra frameworks/meson.build
@@ -0,0 +1,13 @@
+project('xcode extra framework test', 'c')
+
+dep_libs = dependency('OpenGL', method : 'extraframework')
+assert(dep_libs.type_name() == 'extraframeworks', 'type_name is ' + dep_libs.type_name())
+
+dep_main = dependency('Foundation')
+assert(dep_main.type_name() == 'extraframeworks', 'type_name is ' + dep_main.type_name())
+
+dep_py = dependency('python', method : 'extraframework')
+assert(dep_main.type_name() == 'extraframeworks', 'type_name is ' + dep_main.type_name())
+
+stlib = static_library('stat', 'stat.c', install : true, dependencies: dep_libs)
+exe = executable('prog', 'prog.c', install : true, dependencies: dep_main)
diff --git a/test cases/osx/5 extra frameworks/prog.c b/test cases/osx/5 extra frameworks/prog.c
new file mode 100644
index 0000000..11b7fad
--- /dev/null
+++ b/test cases/osx/5 extra frameworks/prog.c
@@ -0,0 +1,3 @@
+int main(int argc, char **argv) {
+ return 0;
+}
diff --git a/test cases/osx/5 extra frameworks/stat.c b/test cases/osx/5 extra frameworks/stat.c
new file mode 100644
index 0000000..fa76a65
--- /dev/null
+++ b/test cases/osx/5 extra frameworks/stat.c
@@ -0,0 +1 @@
+int func() { return 933; }