From 432565ccd05f9e75a4d2b9d3792c2a988bb87fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20=22sp1rit=22=E2=80=8B?= Date: Mon, 25 Oct 2021 14:40:48 +0200 Subject: [PATCH 1/2] Support for versioned dylibs Right now, rustc fails when trying to link against a versioned dylib crate (e.g. libsomecrate.so.0.1). This patch tries to address this by replacing the simple ends_with dll_suffix check with a better one. --- compiler/rustc_codegen_ssa/src/back/link.rs | 11 +++++++++- compiler/rustc_metadata/src/locator.rs | 24 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index be50911f4e143..6c66776c7e02a 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -2379,7 +2379,16 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>( if let Some(dir) = parent { cmd.include_path(&fix_windows_verbatim_for_gcc(dir)); } - let filestem = cratepath.file_stem().unwrap().to_str().unwrap(); + let filename = cratepath.file_name().unwrap().to_str().unwrap(); + // test if dll_suffix is found within filename (should be, but if it + // isn't falls back to just getting the file stem). Then just gets the + // substring from the beginning to the suffix. This is better than just + // getting the filestem, as it respects versioned libraries. + let filestem = filename + .find(&sess.target.dll_suffix) + .map(|idx| filename.get(0..idx)) + .flatten() + .unwrap_or(cratepath.file_stem().unwrap().to_str().unwrap()); cmd.link_rust_dylib( Symbol::intern(&unlib(&sess.target, filestem)), parent.unwrap_or_else(|| Path::new("")), diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index bbd30c9327a6c..fce45f303e235 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -365,6 +365,25 @@ impl<'a> CrateLocator<'a> { self.find_library_crate("", &mut seen_paths) } + /// Returns true if `file` has a suffix that could be a valid dylib + /// Example (assuming target has .so as dll_suffix): + /// - `libsomecrate.asdf` -> `false` + /// - `libsomecrate.so` -> `true` + /// - `libsomecrate.so.0.1` -> `true` + /// - `libsomecrate.so.a.4` -> `false` + fn test_dylib_suffix(&self, file: &str) -> bool { + // test if the targets dll_suffix is found within the filename. If it + // is check if all of the chars following it are either in range 0x30 + // to 0x39 (0-9) or 0x2E (.). + match file.find(&self.target.dll_suffix) { + Some(idx) => file + .chars() + .skip(idx + self.target.dll_suffix.len()) + .all(|c| c as u32 >= 0x30 && c as u32 <= 0x39 || c as u32 == 0x2E), + None => false, + } + } + fn find_library_crate( &mut self, extra_prefix: &str, @@ -402,7 +421,7 @@ impl<'a> CrateLocator<'a> { (&file[(rlib_prefix.len())..(file.len() - ".rlib".len())], CrateFlavor::Rlib) } else if file.starts_with(&rlib_prefix) && file.ends_with(".rmeta") { (&file[(rlib_prefix.len())..(file.len() - ".rmeta".len())], CrateFlavor::Rmeta) - } else if file.starts_with(&dylib_prefix) && file.ends_with(&self.target.dll_suffix) { + } else if file.starts_with(&dylib_prefix) && self.test_dylib_suffix(file) { ( &file[(dylib_prefix.len())..(file.len() - self.target.dll_suffix.len())], CrateFlavor::Dylib, @@ -681,8 +700,7 @@ impl<'a> CrateLocator<'a> { }; if file.starts_with("lib") && (file.ends_with(".rlib") || file.ends_with(".rmeta")) - || file.starts_with(&self.target.dll_prefix) - && file.ends_with(&self.target.dll_suffix) + || file.starts_with(&self.target.dll_prefix) && self.test_dylib_suffix(file) { // Make sure there's at most one rlib and at most one dylib. // Note to take care and match against the non-canonicalized name: From 15d33db2950e948447941211b4086840aecd0646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20=22sp1rit=22=E2=80=8B?= Date: Tue, 26 Oct 2021 20:09:02 +0200 Subject: [PATCH 2/2] Preliminary support for verbatim linking of crates Due to the fact that if we were not using verbatim linking, linking against a versioned crate will only work if a unversioned symlink to the versioned one is part of the searchpath. This is "preliminary" as right now it only uses the whole filename for GNU-style linkers. It should however, be expanded to any linker that supports verbatim linking. Also added suggestions from @wesleywiser. --- compiler/rustc_codegen_ssa/src/back/link.rs | 24 ++------ compiler/rustc_codegen_ssa/src/back/linker.rs | 6 +- compiler/rustc_metadata/src/locator.rs | 55 ++++++++++++------- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 6c66776c7e02a..b6e76430d0a95 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -3,6 +3,7 @@ use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorReported, Handler}; use rustc_fs_util::fix_windows_verbatim_for_gcc; use rustc_hir::def_id::CrateNum; +use rustc_metadata::locator::{get_dylib_symbol_name, unlib}; use rustc_middle::middle::dependency_format::Linkage; use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip}; use rustc_session::config::{OutputFilenames, OutputType, PrintRequest}; @@ -17,7 +18,7 @@ use rustc_span::symbol::Symbol; use rustc_target::abi::Endian; use rustc_target::spec::crt_objects::{CrtObjects, CrtObjectsFallback}; use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor, SplitDebuginfo}; -use rustc_target::spec::{PanicStrategy, RelocModel, RelroLevel, SanitizerSet, Target}; +use rustc_target::spec::{PanicStrategy, RelocModel, RelroLevel, SanitizerSet}; use super::archive::{find_library, ArchiveBuilder}; use super::command::Command; @@ -2252,11 +2253,6 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>( add_static_crate::(cmd, sess, codegen_results, tmpdir, crate_type, cnum); } - // Converts a library file-stem into a cc -l argument - fn unlib<'a>(target: &Target, stem: &'a str) -> &'a str { - if stem.starts_with("lib") && !target.is_like_windows { &stem[3..] } else { stem } - } - // Adds the static "rlib" versions of all crates to the command line. // There's a bit of magic which happens here specifically related to LTO, // namely that we remove upstream object files. @@ -2380,19 +2376,9 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>( cmd.include_path(&fix_windows_verbatim_for_gcc(dir)); } let filename = cratepath.file_name().unwrap().to_str().unwrap(); - // test if dll_suffix is found within filename (should be, but if it - // isn't falls back to just getting the file stem). Then just gets the - // substring from the beginning to the suffix. This is better than just - // getting the filestem, as it respects versioned libraries. - let filestem = filename - .find(&sess.target.dll_suffix) - .map(|idx| filename.get(0..idx)) - .flatten() - .unwrap_or(cratepath.file_stem().unwrap().to_str().unwrap()); - cmd.link_rust_dylib( - Symbol::intern(&unlib(&sess.target, filestem)), - parent.unwrap_or_else(|| Path::new("")), - ); + let symbol_name = get_dylib_symbol_name(filename, &sess.target) + .unwrap_or(unlib(&sess.target, cratepath.file_stem().unwrap().to_str().unwrap())); + cmd.link_rust_dylib(Symbol::intern(symbol_name), cratepath); } } diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 429dc45d6a4c4..3a52df2542ed1 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -464,9 +464,9 @@ impl<'a> Linker for GccLinker<'a> { self.linker_arg("-znorelro"); } - fn link_rust_dylib(&mut self, lib: Symbol, _path: &Path) { + fn link_rust_dylib(&mut self, _lib: Symbol, path: &Path) { self.hint_dynamic(); - self.cmd.arg(format!("-l{}", lib)); + self.cmd.arg(format!("-l:{}", path.file_name().unwrap().to_str().unwrap())); } fn link_framework(&mut self, framework: Symbol, as_needed: bool) { @@ -837,7 +837,7 @@ impl<'a> Linker for MsvcLinker<'a> { // check to see if the file is there and just omit linking to it if it's // not present. let name = format!("{}.dll.lib", lib); - if path.join(&name).exists() { + if path.parent().unwrap_or_else(|| Path::new("")).join(&name).exists() { self.cmd.arg(name); } } diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index fce45f303e235..5e56a4a909449 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -238,6 +238,35 @@ use std::path::{Path, PathBuf}; use std::{cmp, fmt, fs}; use tracing::{debug, info, warn}; +// Converts a library file-stem into a cc -l argument +pub fn unlib<'a>(target: &Target, stem: &'a str) -> &'a str { + if stem.starts_with("lib") && !target.is_like_windows { &stem[3..] } else { stem } +} + +/// Returns Some(symbol_name) if `file` could be a valid dylib +/// Example (assuming target is GNU/Linux): +/// - `libsomecrate.asdf` -> `None` +/// - `libsomecrate.so` -> `Some(somecreate)` +/// - `libsomecrate.so.0.1` -> `Some(somecreate)` +/// - `libsomecrate.so.a.4` -> `None` +pub fn get_dylib_symbol_name<'a>(file: &'a str, target: &Target) -> Option<&'a str> { + // test if the targets dll_suffix is found within the filename. If it + // is check if all of the chars following it are either a digit (0-9) + // or a dot. + file.find(&target.dll_suffix) + .map(|idx| { + match file + .chars() + .skip(idx + idx + target.dll_suffix.len()) + .all(|c| c.is_ascii_digit() || c == '.') + { + true => file.get(0..idx).map(|s| unlib(target, s)), + false => None, + } + }) + .flatten() +} + #[derive(Clone)] crate struct CrateLocator<'a> { // Immutable per-session configuration. @@ -365,25 +394,6 @@ impl<'a> CrateLocator<'a> { self.find_library_crate("", &mut seen_paths) } - /// Returns true if `file` has a suffix that could be a valid dylib - /// Example (assuming target has .so as dll_suffix): - /// - `libsomecrate.asdf` -> `false` - /// - `libsomecrate.so` -> `true` - /// - `libsomecrate.so.0.1` -> `true` - /// - `libsomecrate.so.a.4` -> `false` - fn test_dylib_suffix(&self, file: &str) -> bool { - // test if the targets dll_suffix is found within the filename. If it - // is check if all of the chars following it are either in range 0x30 - // to 0x39 (0-9) or 0x2E (.). - match file.find(&self.target.dll_suffix) { - Some(idx) => file - .chars() - .skip(idx + self.target.dll_suffix.len()) - .all(|c| c as u32 >= 0x30 && c as u32 <= 0x39 || c as u32 == 0x2E), - None => false, - } - } - fn find_library_crate( &mut self, extra_prefix: &str, @@ -421,7 +431,9 @@ impl<'a> CrateLocator<'a> { (&file[(rlib_prefix.len())..(file.len() - ".rlib".len())], CrateFlavor::Rlib) } else if file.starts_with(&rlib_prefix) && file.ends_with(".rmeta") { (&file[(rlib_prefix.len())..(file.len() - ".rmeta".len())], CrateFlavor::Rmeta) - } else if file.starts_with(&dylib_prefix) && self.test_dylib_suffix(file) { + } else if file.starts_with(&dylib_prefix) + && get_dylib_symbol_name(file, &self.target).is_some() + { ( &file[(dylib_prefix.len())..(file.len() - self.target.dll_suffix.len())], CrateFlavor::Dylib, @@ -700,7 +712,8 @@ impl<'a> CrateLocator<'a> { }; if file.starts_with("lib") && (file.ends_with(".rlib") || file.ends_with(".rmeta")) - || file.starts_with(&self.target.dll_prefix) && self.test_dylib_suffix(file) + || file.starts_with(&self.target.dll_prefix) + && get_dylib_symbol_name(file, &self.target).is_some() { // Make sure there's at most one rlib and at most one dylib. // Note to take care and match against the non-canonicalized name: