Skip to content

Commit 1086aff

Browse files
committed
Auto merge of #126094 - petrochenkov:libsearch, r=michaelwoerister
linker: Link dylib crates by path Linkers seem to support linking dynamic libraries by path. Not sure why the previous scheme with splitting the path into a directory (passed with `-L`) and a name (passed with `-l`) was used (upd: likely due to #126094 (comment)). When we split a library path `some/dir/libfoo.so` into `-L some/dir` and `-l foo` we add `some/dir` to search directories for *all* libraries looked up by the linker, not just `foo`, and `foo` is also looked up in *all* search directories not just `some/dir`. Technically we may find some unintended libraries this way. Therefore linking dylibs via a full path is both simpler and more reliable. It also makes the set of search directories more easily reproducible when we need to lookup some native library manually (like in #123436).
2 parents c872a14 + 565ddfb commit 1086aff

File tree

4 files changed

+114
-85
lines changed

4 files changed

+114
-85
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

+12-30
Original file line numberDiff line numberDiff line change
@@ -2817,6 +2817,15 @@ fn rehome_sysroot_lib_dir(sess: &Session, lib_dir: &Path) -> PathBuf {
28172817
}
28182818
}
28192819

2820+
fn rehome_lib_path(sess: &Session, path: &Path) -> PathBuf {
2821+
if let Some(dir) = path.parent() {
2822+
let file_name = path.file_name().expect("library path has no file name component");
2823+
rehome_sysroot_lib_dir(sess, dir).join(file_name)
2824+
} else {
2825+
fix_windows_verbatim_for_gcc(path)
2826+
}
2827+
}
2828+
28202829
// Adds the static "rlib" versions of all crates to the command line.
28212830
// There's a bit of magic which happens here specifically related to LTO,
28222831
// namely that we remove upstream object files.
@@ -2847,15 +2856,8 @@ fn add_static_crate(
28472856
let src = &codegen_results.crate_info.used_crate_source[&cnum];
28482857
let cratepath = &src.rlib.as_ref().unwrap().0;
28492858

2850-
let mut link_upstream = |path: &Path| {
2851-
let rlib_path = if let Some(dir) = path.parent() {
2852-
let file_name = path.file_name().expect("rlib path has no file name path component");
2853-
rehome_sysroot_lib_dir(sess, dir).join(file_name)
2854-
} else {
2855-
fix_windows_verbatim_for_gcc(path)
2856-
};
2857-
cmd.link_staticlib_by_path(&rlib_path, false);
2858-
};
2859+
let mut link_upstream =
2860+
|path: &Path| cmd.link_staticlib_by_path(&rehome_lib_path(sess, path), false);
28592861

28602862
if !are_upstream_rust_objects_already_included(sess)
28612863
|| ignored_for_lto(sess, &codegen_results.crate_info, cnum)
@@ -2919,27 +2921,7 @@ fn add_static_crate(
29192921

29202922
// Same thing as above, but for dynamic crates instead of static crates.
29212923
fn add_dynamic_crate(cmd: &mut dyn Linker, sess: &Session, cratepath: &Path) {
2922-
// Just need to tell the linker about where the library lives and
2923-
// what its name is
2924-
let parent = cratepath.parent();
2925-
// When producing a dll, the MSVC linker may not actually emit a
2926-
// `foo.lib` file if the dll doesn't actually export any symbols, so we
2927-
// check to see if the file is there and just omit linking to it if it's
2928-
// not present.
2929-
if sess.target.is_like_msvc && !cratepath.with_extension("dll.lib").exists() {
2930-
return;
2931-
}
2932-
if let Some(dir) = parent {
2933-
cmd.include_path(&rehome_sysroot_lib_dir(sess, dir));
2934-
}
2935-
// "<dir>/name.dll -> name.dll" on windows-msvc
2936-
// "<dir>/name.dll -> name" on windows-gnu
2937-
// "<dir>/libname.<ext> -> name" elsewhere
2938-
let stem = if sess.target.is_like_msvc { cratepath.file_name() } else { cratepath.file_stem() };
2939-
let stem = stem.unwrap().to_str().unwrap();
2940-
// Convert library file-stem into a cc -l argument.
2941-
let prefix = if stem.starts_with("lib") && !sess.target.is_like_windows { 3 } else { 0 };
2942-
cmd.link_dylib_by_name(&stem[prefix..], false, true);
2924+
cmd.link_dylib_by_path(&rehome_lib_path(sess, cratepath), true);
29432925
}
29442926

29452927
fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {

compiler/rustc_codegen_ssa/src/back/linker.rs

+82-55
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,12 @@ pub trait Linker {
268268
false
269269
}
270270
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path);
271-
fn link_dylib_by_name(&mut self, name: &str, verbatim: bool, as_needed: bool);
271+
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
272+
bug!("dylib linked with unsupported linker")
273+
}
274+
fn link_dylib_by_path(&mut self, _path: &Path, _as_needed: bool) {
275+
bug!("dylib linked with unsupported linker")
276+
}
272277
fn link_framework_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
273278
bug!("framework linked with unsupported linker")
274279
}
@@ -403,28 +408,53 @@ impl<'a> GccLinker<'a> {
403408
}
404409
} else {
405410
self.link_or_cc_arg("-shared");
406-
if self.sess.target.is_like_windows {
407-
// The output filename already contains `dll_suffix` so
408-
// the resulting import library will have a name in the
409-
// form of libfoo.dll.a
410-
let implib_name =
411-
out_filename.file_name().and_then(|file| file.to_str()).map(|file| {
412-
format!(
413-
"{}{}{}",
414-
self.sess.target.staticlib_prefix,
415-
file,
416-
self.sess.target.staticlib_suffix
417-
)
418-
});
419-
if let Some(implib_name) = implib_name {
420-
let implib = out_filename.parent().map(|dir| dir.join(&implib_name));
421-
if let Some(implib) = implib {
422-
self.link_arg(&format!("--out-implib={}", (*implib).to_str().unwrap()));
423-
}
411+
if let Some(name) = out_filename.file_name() {
412+
if self.sess.target.is_like_windows {
413+
// The output filename already contains `dll_suffix` so
414+
// the resulting import library will have a name in the
415+
// form of libfoo.dll.a
416+
let mut implib_name = OsString::from(&*self.sess.target.staticlib_prefix);
417+
implib_name.push(name);
418+
implib_name.push(&*self.sess.target.staticlib_suffix);
419+
let mut out_implib = OsString::from("--out-implib=");
420+
out_implib.push(out_filename.with_file_name(implib_name));
421+
self.link_arg(out_implib);
422+
} else {
423+
// When dylibs are linked by a full path this value will get into `DT_NEEDED`
424+
// instead of the full path, so the library can be later found in some other
425+
// location than that specific path.
426+
let mut soname = OsString::from("-soname=");
427+
soname.push(name);
428+
self.link_arg(soname);
424429
}
425430
}
426431
}
427432
}
433+
434+
fn with_as_needed(&mut self, as_needed: bool, f: impl FnOnce(&mut Self)) {
435+
if !as_needed {
436+
if self.sess.target.is_like_osx {
437+
// FIXME(81490): ld64 doesn't support these flags but macOS 11
438+
// has -needed-l{} / -needed_library {}
439+
// but we have no way to detect that here.
440+
self.sess.dcx().emit_warn(errors::Ld64UnimplementedModifier);
441+
} else if self.is_gnu && !self.sess.target.is_like_windows {
442+
self.link_arg("--no-as-needed");
443+
} else {
444+
self.sess.dcx().emit_warn(errors::LinkerUnsupportedModifier);
445+
}
446+
}
447+
448+
f(self);
449+
450+
if !as_needed {
451+
if self.sess.target.is_like_osx {
452+
// See above FIXME comment
453+
} else if self.is_gnu && !self.sess.target.is_like_windows {
454+
self.link_arg("--as-needed");
455+
}
456+
}
457+
}
428458
}
429459

430460
impl<'a> Linker for GccLinker<'a> {
@@ -506,27 +536,18 @@ impl<'a> Linker for GccLinker<'a> {
506536
// to the linker.
507537
return;
508538
}
509-
if !as_needed {
510-
if self.sess.target.is_like_osx {
511-
// FIXME(81490): ld64 doesn't support these flags but macOS 11
512-
// has -needed-l{} / -needed_library {}
513-
// but we have no way to detect that here.
514-
self.sess.dcx().emit_warn(errors::Ld64UnimplementedModifier);
515-
} else if self.is_gnu && !self.sess.target.is_like_windows {
516-
self.link_arg("--no-as-needed");
517-
} else {
518-
self.sess.dcx().emit_warn(errors::LinkerUnsupportedModifier);
519-
}
520-
}
521539
self.hint_dynamic();
522-
self.link_or_cc_arg(format!("-l{}{name}", if verbatim && self.is_gnu { ":" } else { "" },));
523-
if !as_needed {
524-
if self.sess.target.is_like_osx {
525-
// See above FIXME comment
526-
} else if self.is_gnu && !self.sess.target.is_like_windows {
527-
self.link_arg("--as-needed");
528-
}
529-
}
540+
self.with_as_needed(as_needed, |this| {
541+
let colon = if verbatim && this.is_gnu { ":" } else { "" };
542+
this.link_or_cc_arg(format!("-l{colon}{name}"));
543+
});
544+
}
545+
546+
fn link_dylib_by_path(&mut self, path: &Path, as_needed: bool) {
547+
self.hint_dynamic();
548+
self.with_as_needed(as_needed, |this| {
549+
this.link_or_cc_arg(path);
550+
})
530551
}
531552

532553
fn link_framework_by_name(&mut self, name: &str, _verbatim: bool, as_needed: bool) {
@@ -861,6 +882,15 @@ impl<'a> Linker for MsvcLinker<'a> {
861882
self.link_arg(format!("{}{}", name, if verbatim { "" } else { ".lib" }));
862883
}
863884

885+
fn link_dylib_by_path(&mut self, path: &Path, _as_needed: bool) {
886+
// When producing a dll, MSVC linker may not emit an implib file if the dll doesn't export
887+
// any symbols, so we skip linking if the implib file is not present.
888+
let implib_path = path.with_extension("dll.lib");
889+
if implib_path.exists() {
890+
self.link_or_cc_arg(implib_path);
891+
}
892+
}
893+
864894
fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
865895
let prefix = if whole_archive { "/WHOLEARCHIVE:" } else { "" };
866896
let suffix = if verbatim { "" } else { ".lib" };
@@ -1083,6 +1113,10 @@ impl<'a> Linker for EmLinker<'a> {
10831113
self.link_or_cc_args(&["-l", name]);
10841114
}
10851115

1116+
fn link_dylib_by_path(&mut self, path: &Path, _as_needed: bool) {
1117+
self.link_or_cc_arg(path);
1118+
}
1119+
10861120
fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, _whole_archive: bool) {
10871121
self.link_or_cc_args(&["-l", name]);
10881122
}
@@ -1240,6 +1274,10 @@ impl<'a> Linker for WasmLd<'a> {
12401274
self.link_or_cc_args(&["-l", name]);
12411275
}
12421276

1277+
fn link_dylib_by_path(&mut self, path: &Path, _as_needed: bool) {
1278+
self.link_or_cc_arg(path);
1279+
}
1280+
12431281
fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) {
12441282
if !whole_archive {
12451283
self.link_or_cc_args(&["-l", name]);
@@ -1368,10 +1406,6 @@ impl<'a> Linker for L4Bender<'a> {
13681406

13691407
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
13701408

1371-
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
1372-
bug!("dylibs are not supported on L4Re");
1373-
}
1374-
13751409
fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) {
13761410
self.hint_static();
13771411
if !whole_archive {
@@ -1536,6 +1570,11 @@ impl<'a> Linker for AixLinker<'a> {
15361570
self.link_or_cc_arg(format!("-l{name}"));
15371571
}
15381572

1573+
fn link_dylib_by_path(&mut self, path: &Path, _as_needed: bool) {
1574+
self.hint_dynamic();
1575+
self.link_or_cc_arg(path);
1576+
}
1577+
15391578
fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
15401579
self.hint_static();
15411580
if !whole_archive {
@@ -1721,10 +1760,6 @@ impl<'a> Linker for PtxLinker<'a> {
17211760

17221761
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
17231762

1724-
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
1725-
panic!("external dylibs not supported")
1726-
}
1727-
17281763
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
17291764
panic!("staticlibs not supported")
17301765
}
@@ -1791,10 +1826,6 @@ impl<'a> Linker for LlbcLinker<'a> {
17911826

17921827
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
17931828

1794-
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
1795-
panic!("external dylibs not supported")
1796-
}
1797-
17981829
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
17991830
panic!("staticlibs not supported")
18001831
}
@@ -1866,10 +1897,6 @@ impl<'a> Linker for BpfLinker<'a> {
18661897

18671898
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
18681899

1869-
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
1870-
panic!("external dylibs not supported")
1871-
}
1872-
18731900
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
18741901
panic!("staticlibs not supported")
18751902
}

tests/run-make/dylib-soname/foo.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub fn something() {}

tests/run-make/dylib-soname/rmake.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Checks that produced dylibs have a relative SONAME set, so they don't put "unmovable" full paths
2+
// into DT_NEEDED when used by a full path.
3+
4+
//@ only-linux
5+
//@ ignore-cross-compile
6+
7+
use run_make_support::regex::Regex;
8+
use run_make_support::{cmd, run_in_tmpdir, rustc};
9+
10+
fn main() {
11+
run_in_tmpdir(|| {
12+
rustc().crate_name("foo").crate_type("dylib").input("foo.rs").run();
13+
cmd("readelf")
14+
.arg("-d")
15+
.arg("libfoo.so")
16+
.run()
17+
.assert_stdout_contains("Library soname: [libfoo.so]");
18+
});
19+
}

0 commit comments

Comments
 (0)