Skip to content

Commit c8cd91c

Browse files
dcbakernirbheek
andcommitted
Implement rustc controlled whole-archive linking
Rustc as of version 1.61.0 has support for controlling when whole-archive linking takes place, previous to this it tried to make a good guess about what you wanted, which worked most of the time. This is now implemented. Additionally, rustc makes some assumptions about library names (specifically static names), that meson does not keep. This can be fixed with rustc 1.67, where a new +verbatim modifier has been added. We can then force rustc to use the name we give it. Before that, we can sneak through `/WHOELARCHIVE:` in cases of dynamic linking (into a dll or exe), but we can't force the archiver to do what we want (rustc considers the archiver to be an implementation detail). The only solution I can come up with is to copy the library to the format that rustc expects. I've run into some issues with that as well, so we warn in that case. The decisions to leave static into static broken on MSVC for 1.61–1.66 was made because: 1) The work around is non-trivial, and we would have to support that workaround for a long time 2) The number of users of Rust in Meson is small 3) The number of users of Rust in Meson on Windows, with MSVC is tiny 4) Using rustup to update rustc on windows is trivial, and solves the problem completely Fixes: #10723 Fixes: #11247 Co-authored-by: Nirbheek Chauhan <[email protected]>
1 parent 91c3364 commit c8cd91c

File tree

14 files changed

+113
-14
lines changed

14 files changed

+113
-14
lines changed

mesonbuild/backend/ninjabackend.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -1901,7 +1901,8 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
19011901
args += output
19021902
linkdirs = mesonlib.OrderedSet()
19031903
external_deps = target.external_deps.copy()
1904-
for d in itertools.chain(target.link_targets, target.link_whole_targets):
1904+
# TODO: we likely need to use verbatim to handle name_prefix and name_suffix
1905+
for d in target.link_targets:
19051906
linkdirs.add(d.subdir)
19061907
if d.uses_rust():
19071908
# specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
@@ -1922,6 +1923,55 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
19221923
# Rust uses -l for non rust dependencies, but we still need to
19231924
# add dylib=foo
19241925
args += ['-l', f'dylib={d.name}']
1926+
1927+
# Since 1.61.0 Rust has a special modifier for whole-archive linking,
1928+
# before that it would treat linking two static libraries as
1929+
# whole-archive linking. However, to make this work we have to disable
1930+
# bundling, which can't be done until 1.63.0… So for 1.61–1.62 we just
1931+
# have to hope that the default cases of +whole-archive are sufficent.
1932+
# See: https://github.com/rust-lang/rust/issues/99429
1933+
if mesonlib.version_compare(rustc.version, '>= 1.63.0'):
1934+
whole_archive = ':+whole-archive,-bundle'
1935+
else:
1936+
whole_archive = ''
1937+
1938+
if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
1939+
verbatim = ',+verbatim'
1940+
else:
1941+
verbatim = ''
1942+
1943+
for d in target.link_whole_targets:
1944+
linkdirs.add(d.subdir)
1945+
if d.uses_rust():
1946+
# specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
1947+
# dependency, so that collisions with libraries in rustc's
1948+
# sysroot don't cause ambiguity
1949+
args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
1950+
project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
1951+
else:
1952+
if rustc.linker.id in {'link', 'lld-link'}:
1953+
if verbatim:
1954+
# If we can use the verbatim modifier, then everything is great
1955+
args += ['-l', f'static{whole_archive}{verbatim}={d.get_outputs()[0]}']
1956+
elif isinstance(target, build.StaticLibrary):
1957+
# If we don't, for static libraries the only option is
1958+
# to make a copy, since we can't pass objects in, or
1959+
# directly affect the archiver. but we're not going to
1960+
# do that given how quickly rustc versions go out of
1961+
# support unless there's a compelling reason to do so.
1962+
# This only affects 1.61–1.66
1963+
mlog.warning('Due to limitations in Rustc versions 1.61–1.66 and meson library naming',
1964+
'whole-archive linking with MSVC may or may not work. Upgrade rustc to',
1965+
'>= 1.67. A best effort is being made, but likely won\'t work')
1966+
args += ['-l', f'static={d.name}']
1967+
else:
1968+
# When doing dynamic linking (binaries and [c]dylibs),
1969+
# we can instead just proxy the correct arguments to the linker
1970+
for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]):
1971+
args += ['-C', f'link-arg={link_whole_arg}']
1972+
else:
1973+
args += ['-l', f'static{whole_archive}={d.name}']
1974+
external_deps.extend(d.external_deps)
19251975
for e in external_deps:
19261976
for a in e.get_link_args():
19271977
if a.endswith(('.dll', '.so', '.dylib')):

test cases/rust/2 sharedlib/meson.build

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
project('rust shared library', 'rust')
1+
project('rust shared library', 'rust', 'c')
22

33
if host_machine.system() == 'darwin'
44
error('MESON_SKIP_TEST: does not work right on macos, please fix!')
55
endif
66

7-
l = shared_library('stuff', 'stuff.rs', install : true)
7+
s = static_library('static', 'value.c')
8+
l = shared_library('stuff', 'stuff.rs', link_whole : s, install : true)
89
e = executable('prog', 'prog.rs', link_with : l, install : true)
910

1011
if build_machine.system() == 'windows'

test cases/rust/2 sharedlib/stuff.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
#![crate_name = "stuff"]
22

3-
pub fn explore() -> &'static str { "librarystring" }
3+
extern "C" {
4+
fn c_value() -> i32;
5+
}
6+
7+
pub fn explore() -> String {
8+
unsafe {
9+
format!("library{}string", c_value())
10+
}
11+
}

test cases/rust/2 sharedlib/value.c

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int c_value(void) {
2+
return 7;
3+
}
+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
project('rust static library', 'rust')
1+
project('rust static library', 'rust', 'c')
22

3-
l = static_library('stuff', 'stuff.rs', install : true)
3+
o = static_library('other', 'other.rs')
4+
v = static_library('value', 'value.c')
5+
l = static_library('stuff', 'stuff.rs', link_whole : [o, v], install : true)
46
e = executable('prog', 'prog.rs', link_with : l, install : true)
57
test('linktest', e)

test cases/rust/3 staticlib/other.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub fn explore(
2+
value: i32,
3+
) -> String {
4+
format!("library{}string", value)
5+
}

test cases/rust/3 staticlib/prog.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
extern crate stuff;
22

3-
fn main() { println!("printing: {}", stuff::explore()); }
3+
fn main() {
4+
println!("printing: {}", stuff::explore());
5+
}

test cases/rust/3 staticlib/stuff.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
#![crate_name = "stuff"]
22

3-
pub fn explore() -> &'static str { "librarystring" }
3+
extern crate other;
4+
5+
extern "C" {
6+
fn c_explore_value() -> i32;
7+
}
8+
9+
pub fn explore(
10+
) -> String {
11+
unsafe {
12+
other::explore(c_explore_value())
13+
}
14+
}

test cases/rust/3 staticlib/value.c

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int
2+
c_explore_value (void)
3+
{
4+
return 42;
5+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <stdio.h>
2+
3+
void hello_from_rust(void);
4+
5+
static void hello_from_c(void) {
6+
printf("Hello from C!\n");
7+
}
8+
9+
void hello_from_both(void) {
10+
hello_from_c();
11+
hello_from_rust();
12+
}

test cases/rust/5 polyglot static/meson.build

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ deps = [
88

99
extra_winlibs = meson.get_compiler('c').get_id() in ['msvc', 'clang-cl'] ? ['userenv.lib', 'ws2_32.lib', 'bcrypt.lib'] : []
1010

11-
l = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib', install : true)
11+
r = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib')
12+
l = static_library('clib', 'clib.c', link_with : r, install : true)
1213
e = executable('prog', 'prog.c',
1314
dependencies: deps,
1415
link_with : l,
+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#include <stdio.h>
22

3-
void f();
3+
void hello_from_both();
44

55
int main(void) {
6-
printf("Hello from C!\n");
7-
f();
6+
hello_from_both();
87
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![crate_name = "stuff"]
22

33
#[no_mangle]
4-
pub extern fn f() {
4+
pub extern "C" fn hello_from_rust() {
55
println!("Hello from Rust!");
66
}

test cases/rust/5 polyglot static/test.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"installed": [
33
{"type": "exe", "file": "usr/bin/prog"},
44
{"type": "pdb", "file": "usr/bin/prog"},
5-
{"type": "file", "file": "usr/lib/libstuff.a"}
5+
{"type": "file", "file": "usr/lib/libclib.a"}
66
]
77
}

0 commit comments

Comments
 (0)