ninjabackend: rust: record Apple frameworks in rlibs

When rustc builds an rlib, it records -l and -L flags in the rlib's
metadata. Then when the final binary links against that rlib, rustc
knows what native libraries are needed.

Right now, frameworks are passed via -Clink-arg=-F and
-Clink-arg=-framework, which just passes raw flags to the linker. So
if an rlib depends on a framework and is installed, that information is
lost and cargo will fail to produce binaries from the rlib.

Fixes: #10725
This commit is contained in:
Paolo Bonzini
2026-01-29 10:53:38 +01:00
committed by Dylan Baker
parent 9db31dc6f1
commit 4e9ba3decf
6 changed files with 79 additions and 3 deletions

View File

@@ -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'))

View File

@@ -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.

View File

@@ -0,0 +1,7 @@
extern "C" {
fn CFAbsoluteTimeGetCurrent() -> f64;
}
pub fn get_absolute_time() -> f64 {
unsafe { CFAbsoluteTimeGetCurrent() }
}

View File

@@ -0,0 +1,6 @@
extern crate timelib;
fn main() {
let time = timelib::get_absolute_time();
println!("Current CFAbsoluteTime: {}", time);
}

View File

@@ -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)

View File

@@ -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])