Skip to content

Commit 08d5cdd

Browse files
authored
Rollup merge of rust-lang#106560 - bjorn3:support_staticlib_dylib_linking, r=pnkfelix
Support linking to rust dylib with --crate-type staticlib This allows for example dynamically linking libstd, while statically linking the user crate into an executable or C dynamic library. For this two unstable flags (`-Z staticlib-allow-rdylib-deps` and `-Z staticlib-prefer-dynamic`) are introduced. Without the former you get an error. The latter is the equivalent to `-C prefer-dynamic` for the staticlib crate type to indicate that dynamically linking is preferred when both options are available, like for libstd. Care must be taken to ensure that no crate ends up being merged into two distinct staticlibs that are linked together. Doing so will cause a linker error at best and undefined behavior at worst. In addition two distinct staticlibs compiled by different rustc may not be combined under any circumstances due to some rustc private symbols not being mangled. To successfully link a staticlib, `--print native-static-libs` can be used while compiling to ask rustc for the linker flags necessary when linking the staticlib. This is an existing flag which previously only listed native libraries. It has been extended to list rust dylibs too. Trying to locate libstd yourself to link against it is not supported and may break if for example the libstd of multiple rustc versions are put in the same directory. For an example on how to use this see the `src/test/run-make-fulldeps/staticlib-dylib-linkage/` test.
2 parents 6df24cc + f1cf67b commit 08d5cdd

File tree

8 files changed

+141
-15
lines changed

8 files changed

+141
-15
lines changed

Diff for: compiler/rustc_codegen_ssa/src/back/link.rs

+70-5
Original file line numberDiff line numberDiff line change
@@ -544,12 +544,38 @@ fn link_staticlib<'a>(
544544

545545
ab.build(out_filename);
546546

547-
if !all_native_libs.is_empty() {
548-
if sess.opts.prints.contains(&PrintRequest::NativeStaticLibs) {
549-
print_native_static_libs(sess, &all_native_libs);
547+
let crates = codegen_results.crate_info.used_crates.iter();
548+
549+
let fmts = codegen_results
550+
.crate_info
551+
.dependency_formats
552+
.iter()
553+
.find_map(|&(ty, ref list)| if ty == CrateType::Staticlib { Some(list) } else { None })
554+
.expect("no dependency formats for staticlib");
555+
556+
let mut all_rust_dylibs = vec![];
557+
for &cnum in crates {
558+
match fmts.get(cnum.as_usize() - 1) {
559+
Some(&Linkage::Dynamic) => {}
560+
_ => continue,
561+
}
562+
let crate_name = codegen_results.crate_info.crate_name[&cnum];
563+
let used_crate_source = &codegen_results.crate_info.used_crate_source[&cnum];
564+
if let Some((path, _)) = &used_crate_source.dylib {
565+
all_rust_dylibs.push(&**path);
566+
} else {
567+
if used_crate_source.rmeta.is_some() {
568+
sess.emit_fatal(errors::LinkRlibError::OnlyRmetaFound { crate_name });
569+
} else {
570+
sess.emit_fatal(errors::LinkRlibError::NotFound { crate_name });
571+
}
550572
}
551573
}
552574

575+
if sess.opts.prints.contains(&PrintRequest::NativeStaticLibs) {
576+
print_native_static_libs(sess, &all_native_libs, &all_rust_dylibs);
577+
}
578+
553579
Ok(())
554580
}
555581

@@ -1289,8 +1315,12 @@ enum RlibFlavor {
12891315
StaticlibBase,
12901316
}
12911317

1292-
fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLib]) {
1293-
let lib_args: Vec<_> = all_native_libs
1318+
fn print_native_static_libs(
1319+
sess: &Session,
1320+
all_native_libs: &[NativeLib],
1321+
all_rust_dylibs: &[&Path],
1322+
) {
1323+
let mut lib_args: Vec<_> = all_native_libs
12941324
.iter()
12951325
.filter(|l| relevant_lib(sess, l))
12961326
.filter_map(|lib| {
@@ -1319,6 +1349,41 @@ fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLib]) {
13191349
}
13201350
})
13211351
.collect();
1352+
for path in all_rust_dylibs {
1353+
// FIXME deduplicate with add_dynamic_crate
1354+
1355+
// Just need to tell the linker about where the library lives and
1356+
// what its name is
1357+
let parent = path.parent();
1358+
if let Some(dir) = parent {
1359+
let dir = fix_windows_verbatim_for_gcc(dir);
1360+
if sess.target.is_like_msvc {
1361+
let mut arg = String::from("/LIBPATH:");
1362+
arg.push_str(&dir.display().to_string());
1363+
lib_args.push(arg);
1364+
} else {
1365+
lib_args.push("-L".to_owned());
1366+
lib_args.push(dir.display().to_string());
1367+
}
1368+
}
1369+
let stem = path.file_stem().unwrap().to_str().unwrap();
1370+
// Convert library file-stem into a cc -l argument.
1371+
let prefix = if stem.starts_with("lib") && !sess.target.is_like_windows { 3 } else { 0 };
1372+
let lib = &stem[prefix..];
1373+
let path = parent.unwrap_or_else(|| Path::new(""));
1374+
if sess.target.is_like_msvc {
1375+
// When producing a dll, the MSVC linker may not actually emit a
1376+
// `foo.lib` file if the dll doesn't actually export any symbols, so we
1377+
// check to see if the file is there and just omit linking to it if it's
1378+
// not present.
1379+
let name = format!("{}.dll.lib", lib);
1380+
if path.join(&name).exists() {
1381+
lib_args.push(name);
1382+
}
1383+
} else {
1384+
lib_args.push(format!("-l{}", lib));
1385+
}
1386+
}
13221387
if !lib_args.is_empty() {
13231388
sess.emit_note(errors::StaticLibraryNativeArtifacts);
13241389
// Prefix for greppability

Diff for: compiler/rustc_metadata/src/dependency_format.rs

+21-10
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,25 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
8989
// to try to eagerly statically link all dependencies. This is normally
9090
// done for end-product dylibs, not intermediate products.
9191
//
92-
// Treat cdylibs similarly. If `-C prefer-dynamic` is set, the caller may
93-
// be code-size conscious, but without it, it makes sense to statically
94-
// link a cdylib.
95-
CrateType::Dylib | CrateType::Cdylib if !sess.opts.cg.prefer_dynamic => Linkage::Static,
96-
CrateType::Dylib | CrateType::Cdylib => Linkage::Dynamic,
92+
// Treat cdylibs and staticlibs similarly. If `-C prefer-dynamic` is set,
93+
// the caller may be code-size conscious, but without it, it makes sense
94+
// to statically link a cdylib or staticlib. For staticlibs we use
95+
// `-Z staticlib-prefer-dynamic` for now. This may be merged into
96+
// `-C prefer-dynamic` in the future.
97+
CrateType::Dylib | CrateType::Cdylib => {
98+
if sess.opts.cg.prefer_dynamic {
99+
Linkage::Dynamic
100+
} else {
101+
Linkage::Static
102+
}
103+
}
104+
CrateType::Staticlib => {
105+
if sess.opts.unstable_opts.staticlib_prefer_dynamic {
106+
Linkage::Dynamic
107+
} else {
108+
Linkage::Static
109+
}
110+
}
97111

98112
// If the global prefer_dynamic switch is turned off, or the final
99113
// executable will be statically linked, prefer static crate linkage.
@@ -108,9 +122,6 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
108122
// No linkage happens with rlibs, we just needed the metadata (which we
109123
// got long ago), so don't bother with anything.
110124
CrateType::Rlib => Linkage::NotLinked,
111-
112-
// staticlibs must have all static dependencies.
113-
CrateType::Staticlib => Linkage::Static,
114125
};
115126

116127
match preferred_linkage {
@@ -123,9 +134,9 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
123134
return v;
124135
}
125136

126-
// Staticlibs and static executables must have all static dependencies.
137+
// Static executables must have all static dependencies.
127138
// If any are not found, generate some nice pretty errors.
128-
if ty == CrateType::Staticlib
139+
if (ty == CrateType::Staticlib && !sess.opts.unstable_opts.staticlib_allow_rdylib_deps)
129140
|| (ty == CrateType::Executable
130141
&& sess.crt_static(Some(ty))
131142
&& !sess.target.crt_static_allows_dylibs)

Diff for: compiler/rustc_session/src/options.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,10 @@ options! {
16761676
#[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")]
16771677
stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED],
16781678
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
1679+
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
1680+
"allow staticlibs to have rust dylib dependencies"),
1681+
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
1682+
"prefer dynamic linking to static linking for staticlibs (default: no)"),
16791683
strict_init_checks: bool = (false, parse_bool, [TRACKED],
16801684
"control if mem::uninitialized and mem::zeroed panic on more UB"),
16811685
strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
include ../tools.mk
2+
3+
all:
4+
$(RUSTC) -C prefer-dynamic bar.rs
5+
$(RUSTC) foo.rs --crate-type staticlib --print native-static-libs \
6+
-Z staticlib-allow-rdylib-deps 2>&1 | grep 'note: native-static-libs: ' \
7+
| sed 's/note: native-static-libs: \(.*\)/\1/' > $(TMPDIR)/libs.txt
8+
cat $(TMPDIR)/libs.txt
9+
10+
ifdef IS_MSVC
11+
$(CC) $(CFLAGS) foo.c $(TMPDIR)/foo.lib $(call OUT_EXE,foo) /link $$(cat $(TMPDIR)/libs.txt)
12+
else
13+
$(CC) $(CFLAGS) foo.c -L $(TMPDIR) -lfoo $$(cat $(TMPDIR)/libs.txt) -o $(call RUN_BINFILE,foo)
14+
endif
15+
16+
$(call RUN,foo)
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![crate_type = "dylib"]
2+
3+
pub fn bar() {
4+
println!("hello!");
5+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <assert.h>
2+
3+
extern void foo();
4+
extern unsigned bar(unsigned a, unsigned b);
5+
6+
int main() {
7+
foo();
8+
assert(bar(1, 2) == 3);
9+
return 0;
10+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![crate_type = "staticlib"]
2+
3+
extern crate bar;
4+
5+
#[no_mangle]
6+
pub extern "C" fn foo() {
7+
bar::bar();
8+
}
9+
10+
#[no_mangle]
11+
pub extern "C" fn bar(a: u32, b: u32) -> u32 {
12+
a + b
13+
}

Diff for: tests/rustdoc-ui/z-help.stdout

+2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
by the linker
172172
-Z src-hash-algorithm=val -- hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)
173173
-Z stack-protector=val -- control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)
174+
-Z staticlib-allow-rdylib-deps=val -- allow staticlibs to have rust dylib dependencies
175+
-Z staticlib-prefer-dynamic=val -- prefer dynamic linking to static linking for staticlibs (default: no)
174176
-Z strict-init-checks=val -- control if mem::uninitialized and mem::zeroed panic on more UB
175177
-Z strip=val -- tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)
176178
-Z symbol-mangling-version=val -- which mangling version to use for symbol names ('legacy' (default) or 'v0')

0 commit comments

Comments
 (0)