Skip to content

Commit 2ba6dc4

Browse files
committed
rust: Fix linking with C libraries (again)
Pass link arguments directly down to linker by using `-C link-args=` instead of letting rustc/linker resolve `-l` arguments. This solves problems with e.g. +verbatim not being portable. However, when building a rlib/staticlib we should still use `-l` arguments because that allows rustc to bundle static libraries we link-whole. In that case, since there is no platform specific dynamic linker, +verbatim works. This also fix installed staticlib that now bundle uninstalled static libraries it links to (recursively). This is done by putting them all into self.link_whole_targets instead of putting their objects into self.objects, and let rustc do the bundling. This has the extra advantage that rustc can bundle static libries built with CustomTarget. Disable bundling in all other cases, otherwise we could end up with duplicated objects in static libraries, in diamond dependency graph case. Fixes: #12484
1 parent d0a7a20 commit 2ba6dc4

File tree

16 files changed

+168
-82
lines changed

16 files changed

+168
-82
lines changed

mesonbuild/backend/ninjabackend.py

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,23 +1962,22 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
19621962
except KeyError:
19631963
pass
19641964

1965-
# Since 1.61.0 Rust has a special modifier for whole-archive linking,
1966-
# before that it would treat linking two static libraries as
1967-
# whole-archive linking. However, to make this work we have to disable
1968-
# bundling, which can't be done until 1.63.0… So for 1.61–1.62 we just
1969-
# have to hope that the default cases of +whole-archive are sufficient.
1970-
# See: https://github.com/rust-lang/rust/issues/99429
1971-
if mesonlib.version_compare(rustc.version, '>= 1.63.0'):
1972-
whole_archive = '+whole-archive,-bundle'
1973-
else:
1974-
whole_archive = ''
1975-
1976-
# FIXME: Seems broken on MacOS: https://github.com/rust-lang/rust/issues/116674
1977-
if mesonlib.version_compare(rustc.version, '>= 1.67.0') and not mesonlib.is_osx():
1965+
if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
19781966
verbatim = '+verbatim'
19791967
else:
19801968
verbatim = ''
19811969

1970+
def _link_library(libname: str, static: bool, bundle: bool = False):
1971+
type_ = 'static' if static else 'dylib'
1972+
modifiers = []
1973+
if not bundle and static:
1974+
modifiers.append('-bundle')
1975+
if verbatim:
1976+
modifiers.append(verbatim)
1977+
if modifiers:
1978+
type_ += ':' + ','.join(modifiers)
1979+
args.extend(['-l', f'{type_}={libname}'])
1980+
19821981
linkdirs = mesonlib.OrderedSet()
19831982
external_deps = target.external_deps.copy()
19841983
target_deps = target.get_dependencies()
@@ -2001,64 +2000,53 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
20012000
if isinstance(d, build.StaticLibrary):
20022001
external_deps.extend(d.external_deps)
20032002

2004-
lib = None
2005-
modifiers = []
2003+
# Pass native libraries directly to the linker with "-C link-arg"
2004+
# because rustc's "-l:+verbatim=" is not portable and we cannot rely
2005+
# on linker to find the right library without using verbatim filename.
2006+
# For example "-lfoo" won't find "foo.so" in the case name_prefix set
2007+
# to "", or would always pick the shared library when both "libfoo.so"
2008+
# and "libfoo.a" are available.
2009+
# See https://doc.rust-lang.org/rustc/command-line-arguments.html#linking-modifiers-verbatim.
2010+
#
2011+
# However, rustc static linker (rlib and staticlib) requires using
2012+
# "-l" argument and does not rely on platform specific dynamic linker.
2013+
lib = self.get_target_filename_for_linking(d)
20062014
link_whole = d in target.link_whole_targets
2007-
if link_whole and whole_archive:
2008-
modifiers.append(whole_archive)
2009-
if verbatim:
2010-
modifiers.append(verbatim)
2011-
lib = self.get_target_filename_for_linking(d)
2012-
elif rustc.linker.id in {'link', 'lld-link'} and isinstance(d, build.StaticLibrary):
2013-
# Rustc doesn't follow Meson's convention that static libraries
2014-
# are called .a, and import libraries are .lib, so we have to
2015-
# manually handle that.
2016-
if link_whole:
2017-
if isinstance(target, build.StaticLibrary):
2018-
# If we don't, for static libraries the only option is
2019-
# to make a copy, since we can't pass objects in, or
2020-
# directly affect the archiver. but we're not going to
2021-
# do that given how quickly rustc versions go out of
2022-
# support unless there's a compelling reason to do so.
2023-
# This only affects 1.61–1.66
2024-
mlog.warning('Due to limitations in Rustc versions 1.61–1.66 and meson library naming,',
2025-
'whole-archive linking with MSVC may or may not work. Upgrade rustc to',
2026-
'>= 1.67. A best effort is being made, but likely won\'t work')
2027-
lib = d.name
2028-
else:
2029-
# When doing dynamic linking (binaries and [c]dylibs),
2030-
# we can instead just proxy the correct arguments to the linker
2031-
for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]):
2032-
args += ['-C', f'link-arg={link_whole_arg}']
2033-
else:
2034-
args += ['-C', f'link-arg={self.get_target_filename_for_linking(d)}']
2015+
if isinstance(target, build.StaticLibrary):
2016+
static = isinstance(d, build.StaticLibrary)
2017+
libname = os.path.basename(lib) if verbatim else d.name
2018+
# rlib does not support bundling. That probably could cause
2019+
# unusable installed rlib if they link to uninstalled static
2020+
# libraries. Installing rlib is not something we generally
2021+
# support anyway.
2022+
# https://github.com/rust-lang/rust/issues/108081
2023+
bundle = cratetype == 'staticlib' and link_whole
2024+
_link_library(libname, static, bundle)
2025+
elif link_whole:
2026+
link_whole_args = rustc.linker.get_link_whole_for([lib])
2027+
args += [f'-Clink-arg={a}' for a in link_whole_args]
20352028
else:
2036-
lib = d.name
2037-
2038-
if lib:
2039-
_type = 'static' if isinstance(d, build.StaticLibrary) else 'dylib'
2040-
if modifiers:
2041-
_type += ':' + ','.join(modifiers)
2042-
args += ['-l', f'{_type}={lib}']
2029+
args.append(f'-Clink-arg={lib}')
20432030

20442031
for e in external_deps:
20452032
for a in e.get_link_args():
20462033
if a in rustc.native_static_libs:
20472034
# Exclude link args that rustc already add by default
2048-
continue
2049-
if a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')):
2050-
dir_, lib = os.path.split(a)
2051-
linkdirs.add(dir_)
2052-
lib, ext = os.path.splitext(lib)
2053-
if lib.startswith('lib'):
2054-
lib = lib[3:]
2055-
_type = 'static' if a.endswith(('.a', '.lib')) else 'dylib'
2056-
args.extend(['-l', f'{_type}={lib}'])
2035+
pass
20572036
elif a.startswith('-L'):
20582037
args.append(a)
2059-
elif a.startswith('-l'):
2060-
_type = 'static' if e.static else 'dylib'
2061-
args.extend(['-l', f'{_type}={a[2:]}'])
2038+
elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')) and isinstance(target, build.StaticLibrary):
2039+
dir_, lib = os.path.split(a)
2040+
linkdirs.add(dir_)
2041+
if not verbatim:
2042+
lib, ext = os.path.splitext(lib)
2043+
if lib.startswith('lib'):
2044+
lib = lib[3:]
2045+
static = a.endswith(('.a', '.lib'))
2046+
_link_library(lib, static)
2047+
else:
2048+
args.append(f'-Clink-arg={a}')
2049+
20622050
for d in linkdirs:
20632051
if d == '':
20642052
d = '.'

mesonbuild/build.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ def link_whole(self, targets: T.List[BuildTargetTypes], promoted: bool = False)
14341434
msg += "Use the 'pic' option to static_library to build with PIC."
14351435
raise InvalidArguments(msg)
14361436
self.check_can_link_together(t)
1437-
if isinstance(self, StaticLibrary) and not self.uses_rust():
1437+
if isinstance(self, StaticLibrary):
14381438
# When we're a static library and we link_whole: to another static
14391439
# library, we need to add that target's objects to ourselves.
14401440
self._bundle_static_library(t, promoted)
@@ -1461,7 +1461,10 @@ def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTy
14611461
t.get_internal_static_libraries_recurse(result)
14621462

14631463
def _bundle_static_library(self, t: T.Union[BuildTargetTypes], promoted: bool = False) -> None:
1464-
if isinstance(t, (CustomTarget, CustomTargetIndex)) or t.uses_rust():
1464+
if self.uses_rust():
1465+
# Rustc can bundle static libraries, no need to extract objects.
1466+
self.link_whole_targets.append(t)
1467+
elif isinstance(t, (CustomTarget, CustomTargetIndex)) or t.uses_rust():
14651468
# To extract objects from a custom target we would have to extract
14661469
# the archive, WIP implementation can be found in
14671470
# https://github.com/mesonbuild/meson/pull/9218.
@@ -1476,7 +1479,8 @@ def _bundle_static_library(self, t: T.Union[BuildTargetTypes], promoted: bool =
14761479
m += (f' Meson had to promote link to link_whole because {self.name!r} is installed but not {t.name!r},'
14771480
f' and thus has to include objects from {t.name!r} to be usable.')
14781481
raise InvalidArguments(m)
1479-
self.objects.append(t.extract_all_objects())
1482+
else:
1483+
self.objects.append(t.extract_all_objects())
14801484

14811485
def check_can_link_together(self, t: BuildTargetTypes) -> None:
14821486
links_with_rust_abi = isinstance(t, BuildTarget) and t.uses_rust_abi()

test cases/rust/15 polyglot sharedlib/adder.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ adder* adder_create(int number) {
1111
return a;
1212
}
1313

14-
// adder_add is implemented in the Rust file.
14+
// adder_add_r is implemented in the Rust file.
15+
int adder_add_r(adder *a, int number);
16+
17+
int adder_add(adder *a, int number)
18+
{
19+
return adder_add_r(a, number);
20+
}
1521

1622
void adder_destroy(adder *a) {
1723
free(a);

test cases/rust/15 polyglot sharedlib/adder.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ pub struct Adder {
33
pub number: i32
44
}
55

6+
extern "C" {
7+
pub fn zero() -> i32;
8+
pub fn zero_static() -> i32;
9+
}
10+
611
#[no_mangle]
7-
pub extern fn adder_add(a: &Adder, number: i32) -> i32 {
8-
return a.number + number;
12+
pub extern fn adder_add_r(a: &Adder, number: i32) -> i32 {
13+
unsafe {
14+
return a.number + number + zero() + zero_static();
15+
}
916
}
Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
project('adder', 'c', 'rust', version: '1.0.0')
22

3-
if build_machine.system() != 'linux'
4-
error('MESON_SKIP_TEST, this test only works on Linux. Patches welcome.')
5-
endif
3+
subdir('zero')
64

7-
thread_dep = dependency('threads')
8-
dl_dep = meson.get_compiler('c').find_library('dl', required: false)
9-
m_dep = meson.get_compiler('c').find_library('m', required: false)
10-
11-
rl = static_library('radder', 'adder.rs', rust_crate_type: 'staticlib')
5+
rl = shared_library('radder', 'adder.rs',
6+
rust_abi: 'c',
7+
link_with: [zero_shared, zero_static])
128

139
l = shared_library('adder', 'adder.c',
14-
c_args: '-DBUILDING_ADDER',
15-
link_with: rl,
16-
version: '1.0.0',
17-
soversion: '1',
18-
link_args: '-Wl,-u,adder_add', # Ensure that Rust code is not removed as unused.
19-
dependencies: [thread_dep, dl_dep, m_dep])
10+
c_args: '-DBUILDING_ADDER',
11+
link_with: rl,
12+
version: '1.0.0',
13+
soversion: '1',
14+
)
2015
test('adder', executable('addertest', 'addertest.c', link_with: l))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# They both have the same name, this tests we use +verbatim to distinguish them
2+
# using their filename. It also ensures we pass the importlib on Windows.
3+
# Those libs are in a subdir as regression test:
4+
# https://github.com/mesonbuild/meson/issues/12484
5+
zero_shared = shared_library('zero', 'zero.c')
6+
zero_static = static_library('zero', 'zero_static.c')
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#if defined _WIN32 || defined __CYGWIN__
2+
#define EXPORT __declspec(dllexport)
3+
#else
4+
#define EXPORT
5+
#endif
6+
7+
EXPORT int zero(void);
8+
9+
int zero(void) {
10+
return 0;
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
int zero_static(void);
2+
3+
int zero_static(void)
4+
{
5+
return 0;
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int c_func(void);
2+
int c_func(void) {
3+
return 123;
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int r3(void);
2+
3+
int main_func(void) {
4+
return r3() == 246 ? 0 : 1;
5+
}

0 commit comments

Comments
 (0)