Skip to content

Commit

Permalink
Implement rustc controlled whole-archive linking
Browse files Browse the repository at this point in the history
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 <nirbheek@centricular.com>
  • Loading branch information
dcbaker and nirbheek committed Feb 23, 2023
1 parent 91c3364 commit c8cd91c
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 14 deletions.
52 changes: 51 additions & 1 deletion mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1901,7 +1901,8 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
args += output
linkdirs = mesonlib.OrderedSet()
external_deps = target.external_deps.copy()
for d in itertools.chain(target.link_targets, target.link_whole_targets):
# TODO: we likely need to use verbatim to handle name_prefix and name_suffix
for d in target.link_targets:
linkdirs.add(d.subdir)
if d.uses_rust():
# specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
Expand All @@ -1922,6 +1923,55 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
# Rust uses -l for non rust dependencies, but we still need to
# add dylib=foo
args += ['-l', f'dylib={d.name}']

# Since 1.61.0 Rust has a special modifier for whole-archive linking,
# before that it would treat linking two static libraries as
# whole-archive linking. However, to make this work we have to disable
# bundling, which can't be done until 1.63.0… So for 1.61–1.62 we just
# have to hope that the default cases of +whole-archive are sufficent.
# See: https://github.com/rust-lang/rust/issues/99429
if mesonlib.version_compare(rustc.version, '>= 1.63.0'):
whole_archive = ':+whole-archive,-bundle'
else:
whole_archive = ''

if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
verbatim = ',+verbatim'
else:
verbatim = ''

for d in target.link_whole_targets:
linkdirs.add(d.subdir)
if d.uses_rust():
# specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
# dependency, so that collisions with libraries in rustc's
# sysroot don't cause ambiguity
args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
else:
if rustc.linker.id in {'link', 'lld-link'}:
if verbatim:
# If we can use the verbatim modifier, then everything is great
args += ['-l', f'static{whole_archive}{verbatim}={d.get_outputs()[0]}']
elif isinstance(target, build.StaticLibrary):
# If we don't, for static libraries the only option is
# to make a copy, since we can't pass objects in, or
# directly affect the archiver. but we're not going to
# do that given how quickly rustc versions go out of
# support unless there's a compelling reason to do so.
# This only affects 1.61–1.66
mlog.warning('Due to limitations in Rustc versions 1.61–1.66 and meson library naming',
'whole-archive linking with MSVC may or may not work. Upgrade rustc to',
'>= 1.67. A best effort is being made, but likely won\'t work')
args += ['-l', f'static={d.name}']
else:
# When doing dynamic linking (binaries and [c]dylibs),
# we can instead just proxy the correct arguments to the linker
for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]):
args += ['-C', f'link-arg={link_whole_arg}']
else:
args += ['-l', f'static{whole_archive}={d.name}']
external_deps.extend(d.external_deps)
for e in external_deps:
for a in e.get_link_args():
if a.endswith(('.dll', '.so', '.dylib')):
Expand Down
5 changes: 3 additions & 2 deletions test cases/rust/2 sharedlib/meson.build
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
project('rust shared library', 'rust')
project('rust shared library', 'rust', 'c')

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

l = shared_library('stuff', 'stuff.rs', install : true)
s = static_library('static', 'value.c')
l = shared_library('stuff', 'stuff.rs', link_whole : s, install : true)
e = executable('prog', 'prog.rs', link_with : l, install : true)

if build_machine.system() == 'windows'
Expand Down
10 changes: 9 additions & 1 deletion test cases/rust/2 sharedlib/stuff.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#![crate_name = "stuff"]

pub fn explore() -> &'static str { "librarystring" }
extern "C" {
fn c_value() -> i32;
}

pub fn explore() -> String {
unsafe {
format!("library{}string", c_value())
}
}
3 changes: 3 additions & 0 deletions test cases/rust/2 sharedlib/value.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int c_value(void) {
return 7;
}
6 changes: 4 additions & 2 deletions test cases/rust/3 staticlib/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
project('rust static library', 'rust')
project('rust static library', 'rust', 'c')

l = static_library('stuff', 'stuff.rs', install : true)
o = static_library('other', 'other.rs')
v = static_library('value', 'value.c')
l = static_library('stuff', 'stuff.rs', link_whole : [o, v], install : true)
e = executable('prog', 'prog.rs', link_with : l, install : true)
test('linktest', e)
5 changes: 5 additions & 0 deletions test cases/rust/3 staticlib/other.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fn explore(
value: i32,
) -> String {
format!("library{}string", value)
}
4 changes: 3 additions & 1 deletion test cases/rust/3 staticlib/prog.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
extern crate stuff;

fn main() { println!("printing: {}", stuff::explore()); }
fn main() {
println!("printing: {}", stuff::explore());
}
13 changes: 12 additions & 1 deletion test cases/rust/3 staticlib/stuff.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
#![crate_name = "stuff"]

pub fn explore() -> &'static str { "librarystring" }
extern crate other;

extern "C" {
fn c_explore_value() -> i32;
}

pub fn explore(
) -> String {
unsafe {
other::explore(c_explore_value())
}
}
5 changes: 5 additions & 0 deletions test cases/rust/3 staticlib/value.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int
c_explore_value (void)
{
return 42;
}
12 changes: 12 additions & 0 deletions test cases/rust/5 polyglot static/clib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <stdio.h>

void hello_from_rust(void);

static void hello_from_c(void) {
printf("Hello from C!\n");
}

void hello_from_both(void) {
hello_from_c();
hello_from_rust();
}
3 changes: 2 additions & 1 deletion test cases/rust/5 polyglot static/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ deps = [

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

l = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib', install : true)
r = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib')
l = static_library('clib', 'clib.c', link_with : r, install : true)
e = executable('prog', 'prog.c',
dependencies: deps,
link_with : l,
Expand Down
5 changes: 2 additions & 3 deletions test cases/rust/5 polyglot static/prog.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#include <stdio.h>

void f();
void hello_from_both();

int main(void) {
printf("Hello from C!\n");
f();
hello_from_both();
}
2 changes: 1 addition & 1 deletion test cases/rust/5 polyglot static/stuff.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![crate_name = "stuff"]

#[no_mangle]
pub extern fn f() {
pub extern "C" fn hello_from_rust() {
println!("Hello from Rust!");
}
2 changes: 1 addition & 1 deletion test cases/rust/5 polyglot static/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"installed": [
{"type": "exe", "file": "usr/bin/prog"},
{"type": "pdb", "file": "usr/bin/prog"},
{"type": "file", "file": "usr/lib/libstuff.a"}
{"type": "file", "file": "usr/lib/libclib.a"}
]
}

0 comments on commit c8cd91c

Please sign in to comment.