diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e0aaa39c4..3c26b39c9 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -32,7 +32,7 @@ from ..mesonlib import ( File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine, ProgressBar, quote_arg, unique_list ) -from ..mesonlib import get_compiler_for_source, has_path_sep, is_parent_path +from ..mesonlib import get_compiler_for_source, has_path_sep, is_parent_path, lookbehind from ..options import OptionKey from .backends import CleanTrees from ..build import GeneratedList, InvalidArguments @@ -2151,10 +2151,21 @@ class NinjaBackend(backends.Backend): args.append(f'-Clink-arg={lib}') for e in external_deps: - for a in e.get_link_args(): - if a.startswith('-L'): + prev: T.Optional[str] = None + for prev, a in lookbehind(e.get_link_args()): + if prev == '-framework': + args.append(f'-lframework={a}') + continue + elif a.startswith('-L'): args.append(a) continue + elif a.startswith('-F'): + path = a[2:] + args.append(f'-Lframework={path}') + continue + elif a == '-framework': + # handled once the framework name is available + continue elif is_library(a): if isinstance(target, build.StaticLibrary): static = a.endswith(('.a', '.lib')) diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index fc7dde019..db935e3c9 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -139,6 +139,7 @@ __all__ = [ 'listify', 'listify_array_value', 'lookahead', + 'lookbehind', 'partition', 'path_is_in_root', 'pickle_load', @@ -2536,6 +2537,24 @@ def get_subproject_dir(directory: str = '.') -> T.Optional[str]: return intr.extract_subproject_dir() or 'subprojects' +def lookbehind(it_: T.Iterable[_T]) -> T.Iterator[T.Tuple[T.Optional[_T], _T]]: + """Get the current value of the iterable, and the previous if possible. + + :param iter: The iterable to look into + :yield: A tuple of the previous value if possible, and the current one + :return: nothing + """ + prev: T.Optional[_T] = None + it: T.Iterator[_T] = iter(it_) + while True: + try: + current = next(it) + yield prev, current + prev = current + except StopIteration: + break + + def lookahead(it_: T.Iterable[_T]) -> T.Iterator[T.Tuple[_T, T.Optional[_T]]]: """Get the current value of the iterable, and the next if possible. diff --git a/test cases/rust/35 apple framework/lib.rs b/test cases/rust/35 apple framework/lib.rs new file mode 100644 index 000000000..05c7d8cb4 --- /dev/null +++ b/test cases/rust/35 apple framework/lib.rs @@ -0,0 +1,7 @@ +extern "C" { + fn CFAbsoluteTimeGetCurrent() -> f64; +} + +pub fn get_absolute_time() -> f64 { + unsafe { CFAbsoluteTimeGetCurrent() } +} diff --git a/test cases/rust/35 apple framework/main.rs b/test cases/rust/35 apple framework/main.rs new file mode 100644 index 000000000..b620c7985 --- /dev/null +++ b/test cases/rust/35 apple framework/main.rs @@ -0,0 +1,6 @@ +extern crate timelib; + +fn main() { + let time = timelib::get_absolute_time(); + println!("Current CFAbsoluteTime: {}", time); +} diff --git a/test cases/rust/35 apple framework/meson.build b/test cases/rust/35 apple framework/meson.build new file mode 100644 index 000000000..4e99f26aa --- /dev/null +++ b/test cases/rust/35 apple framework/meson.build @@ -0,0 +1,13 @@ +project('rust apple framework', ['c', 'rust'], meson_version: '>=1.3.0') + +if host_machine.system() != 'darwin' + error('MESON_SKIP_TEST: this test is only for macOS') +endif + +foundation = dependency('appleframeworks', modules: 'Foundation') + +timelib = static_library('timelib', 'lib.rs', dependencies: foundation, rust_abi: 'rust') +alias_target('timelib', timelib) + +exe = executable('main', 'main.rs', link_with: timelib) +test('main', exe) diff --git a/unittests/darwintests.py b/unittests/darwintests.py index 26dd99641..64e288700 100644 --- a/unittests/darwintests.py +++ b/unittests/darwintests.py @@ -174,3 +174,23 @@ class DarwinTests(BasePlatformTests): # Those RPATHs are no longer valid and should not be present after installation rpaths = self._get_darwin_rpaths(os.path.join(self.installdir, 'usr/lib/libbar.dylib')) self.assertListEqual(rpaths, []) + + @skip_if_not_language('rust') + def test_rust_apple_framework_rlib(self): + ''' + Test that Rust rlibs properly record Apple framework dependencies, + so that external tools (like cargo) can link against them without + meson's help. + ''' + testdir = os.path.join(self.rust_test_dir, '35 apple framework') + self.init(testdir) + # Build only the library, not the executable + self.build(target='timelib') + # Manually invoke rustc to build the executable, using the rlib. + # This simulates what cargo or another build system would do. + rlib = os.path.join(self.builddir, 'libtimelib.rlib') + main_rs = os.path.join(testdir, 'main.rs') + out_exe = os.path.join(self.builddir, 'manual_main') + subprocess.check_call(['rustc', '--extern', f'timelib={rlib}', main_rs, '-o', out_exe]) + # Run the executable to verify it works + subprocess.check_call([out_exe])