diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 43d33d36704d3..6af49d714342b 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -24,11 +24,8 @@ use rustc_hir::attrs::NativeLibKind; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_lint_defs::builtin::LINKER_INFO; use rustc_macros::Diagnostic; +use rustc_metadata::EncodedMetadata; use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file}; -use rustc_metadata::{ - EncodedMetadata, NativeLibSearchFallback, find_native_static_library, - walk_native_lib_search_dirs, -}; use rustc_middle::bug; use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::lint::emit_lint_base; @@ -135,6 +132,173 @@ fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &Crat } } +/// The fallback directories are passed to linker, but not used when rustc does the search, +/// because in the latter case the set of fallback directories cannot always be determined +/// consistently at the moment. +struct NativeLibSearchFallback<'a> { + self_contained_components: LinkSelfContainedComponents, + apple_sdk_root: Option<&'a Path>, +} + +fn walk_native_lib_search_dirs( + sess: &Session, + fallback: Option>, + mut f: impl FnMut(&Path, bool /*is_framework*/) -> ControlFlow, +) -> ControlFlow { + // Library search paths explicitly supplied by user (`-L` on the command line). + for search_path in sess.target_filesearch().cli_search_paths(PathKind::Native) { + f(&search_path.dir, false)?; + } + for search_path in sess.target_filesearch().cli_search_paths(PathKind::Framework) { + // Frameworks are looked up strictly in framework-specific paths. + if search_path.kind != PathKind::All { + f(&search_path.dir, true)?; + } + } + + let Some(NativeLibSearchFallback { self_contained_components, apple_sdk_root }) = fallback + else { + return ControlFlow::Continue(()); + }; + + // The toolchain ships some native library components and self-contained linking was enabled. + // Add the self-contained library directory to search paths. + if self_contained_components.intersects( + LinkSelfContainedComponents::LIBC + | LinkSelfContainedComponents::UNWIND + | LinkSelfContainedComponents::MINGW, + ) { + f(&sess.target_tlib_path.dir.join("self-contained"), false)?; + } + + let has_shared_llvm_apple_darwin = + sess.target.is_like_darwin && sess.target_tlib_path.dir.join("libLLVM.dylib").exists(); + + // Toolchains for some targets may ship `libunwind.a`, but place it into the main sysroot + // library directory instead of the self-contained directories. + // Sanitizer libraries have the same issue and are also linked by name on Apple targets. + // The targets here should be in sync with `copy_third_party_objects` in bootstrap. + // On Apple targets, shared LLVM is linked by name, so when `libLLVM.dylib` is + // present in the target libdir, add that directory to the linker search path. + // FIXME: implement `-Clink-self-contained=+/-unwind,+/-sanitizers`, move the shipped libunwind + // and sanitizers to self-contained directory, and stop adding this search path. + // FIXME: On AIX this also has the side-effect of making the list of library search paths + // non-empty, which is needed or the linker may decide to record the LIBPATH env, if + // defined, as the search path instead of appending the default search paths. + if sess.target.cfg_abi == CfgAbi::Fortanix + || sess.target.os == Os::Linux + || sess.target.os == Os::Fuchsia + || sess.target.is_like_aix + || sess.target.is_like_darwin + && (!sess.sanitizers().is_empty() || has_shared_llvm_apple_darwin) + || sess.target.os == Os::Windows + && sess.target.env == Env::Gnu + && sess.target.cfg_abi == CfgAbi::Llvm + { + f(&sess.target_tlib_path.dir, false)?; + } + + // Mac Catalyst uses the macOS SDK, but to link to iOS-specific frameworks + // we must have the support library stubs in the library search path (#121430). + if let Some(sdk_root) = apple_sdk_root + && sess.target.env == Env::MacAbi + { + f(&sdk_root.join("System/iOSSupport/usr/lib"), false)?; + f(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"), true)?; + } + + ControlFlow::Continue(()) +} + +pub(super) fn try_find_native_static_library( + sess: &Session, + name: &str, + verbatim: bool, +) -> Option { + let default = sess.staticlib_components(verbatim); + let formats = if verbatim { + vec![default] + } else { + // On Windows, static libraries sometimes show up as libfoo.a and other + // times show up as foo.lib + let unix = ("lib", ".a"); + if default == unix { vec![default] } else { vec![default, unix] } + }; + + walk_native_lib_search_dirs(sess, None, |dir, is_framework| { + if !is_framework { + for (prefix, suffix) in &formats { + let test = dir.join(format!("{prefix}{name}{suffix}")); + if test.exists() { + return ControlFlow::Break(test); + } + } + } + ControlFlow::Continue(()) + }) + .break_value() +} + +pub(super) fn try_find_native_dynamic_library( + sess: &Session, + name: &str, + verbatim: bool, +) -> Option { + let default = sess.staticlib_components(verbatim); + let formats = if verbatim { + vec![default] + } else { + // While the official naming convention for MSVC import libraries + // is foo.lib, Meson follows the libfoo.dll.a convention to + // disambiguate .a for static libraries + let meson = ("lib", ".dll.a"); + // and MinGW uses .a altogether + let mingw = ("lib", ".a"); + vec![default, meson, mingw] + }; + + walk_native_lib_search_dirs(sess, None, |dir, is_framework| { + if !is_framework { + for (prefix, suffix) in &formats { + let test = dir.join(format!("{prefix}{name}{suffix}")); + if test.exists() { + return ControlFlow::Break(test); + } + } + } + ControlFlow::Continue(()) + }) + .break_value() +} + +pub(super) fn find_native_static_library(name: &str, verbatim: bool, sess: &Session) -> PathBuf { + try_find_native_static_library(sess, name, verbatim).unwrap_or_else(|| { + sess.dcx().emit_fatal(diagnostics::MissingNativeLibrary::new(name, verbatim)) + }) +} + +/// If `lib` is a static library that is bundled into the rlib as a packed archive, returns the +/// file name of that archive. Returns `None` for libraries that are instead unpacked into loose +/// object files, or not bundled at all. +fn find_bundled_library( + lib: &NativeLib, + sess: &Session, + crate_types: &[CrateType], +) -> Option { + if let NativeLibKind::Static { bundle: Some(true) | None, whole_archive, .. } = lib.kind + && crate_types.iter().any(|t| matches!(t, &CrateType::Rlib | CrateType::StaticLib)) + && (sess.opts.unstable_opts.packed_bundled_libs + || lib.cfg.is_some() + || whole_archive == Some(true)) + { + return find_native_static_library(lib.name.as_str(), lib.verbatim, sess) + .file_name() + .and_then(|s| s.to_str()) + .map(Symbol::intern); + } + None +} + /// Performs the linkage portion of the compilation phase. This will generate all /// of the requested outputs for this compilation session. pub fn link_binary( @@ -374,28 +538,6 @@ pub fn each_linked_rlib( Ok(()) } -/// If `lib` is a static library that is bundled into the rlib as a packed archive, returns the -/// file name of that archive. Returns `None` for libraries that are instead unpacked into loose -/// object files, or not bundled at all. -fn find_bundled_library( - lib: &NativeLib, - sess: &Session, - crate_types: &[CrateType], -) -> Option { - if let NativeLibKind::Static { bundle: Some(true) | None, whole_archive, .. } = lib.kind - && crate_types.iter().any(|t| matches!(t, &CrateType::Rlib | CrateType::StaticLib)) - && (sess.opts.unstable_opts.packed_bundled_libs - || lib.cfg.is_some() - || whole_archive == Some(true)) - { - return find_native_static_library(lib.name.as_str(), lib.verbatim, sess) - .file_name() - .and_then(|s| s.to_str()) - .map(Symbol::intern); - } - None -} - /// Create an 'rlib'. /// /// An rlib in its current incarnation is essentially a renamed .a file (with "dummy" object files). diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 4bb6143e9338b..50a3e7fb7a1d1 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -7,9 +7,6 @@ use std::{env, iter, mem, str}; use find_msvc_tools; use rustc_hir::attrs::WindowsSubsystemKind; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; -use rustc_metadata::{ - find_native_static_library, try_find_native_dynamic_library, try_find_native_static_library, -}; use rustc_middle::bug; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::{ @@ -23,6 +20,9 @@ use tracing::{debug, warn}; use super::command::Command; use super::symbol_export; +use crate::back::link::{ + find_native_static_library, try_find_native_dynamic_library, try_find_native_static_library, +}; use crate::back::symbol_export::allocator_shim_symbols; use crate::base::needs_allocator_shim_for_linking; use crate::{SymbolExport, diagnostics}; diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 1fe52c34e89f4..599a23ec0ddc1 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1340,3 +1340,43 @@ pub(crate) struct LtoProcMacro; #[diag("cannot prefer dynamic linking when performing LTO")] #[note("only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO")] pub(crate) struct DynamicLinkingWithLTO; + +#[derive(Diagnostic)] +#[diag("could not find native static library `{$libname}`, perhaps an -L flag is missing?")] +pub(crate) struct MissingNativeLibrary<'a> { + libname: &'a str, + #[subdiagnostic] + suggest_name: Option>, +} + +impl<'a> MissingNativeLibrary<'a> { + pub(crate) fn new(libname: &'a str, verbatim: bool) -> Self { + // if it looks like the user has provided a complete filename rather just the bare lib name, + // then provide a note that they might want to try trimming the name + let suggested_name = if !verbatim { + if let Some(libname) = libname.strip_circumfix("lib", ".a") { + // this is a unix style filename so trim prefix & suffix + Some(libname) + } else if let Some(libname) = libname.strip_suffix(".lib") { + // this is a Windows style filename so just trim the suffix + Some(libname) + } else { + None + } + } else { + None + }; + + Self { + libname, + suggest_name: suggested_name + .map(|suggested_name| SuggestLibraryName { suggested_name }), + } + } +} + +#[derive(Subdiagnostic)] +#[help("only provide the library name `{$suggested_name}`, not the full filename")] +pub(crate) struct SuggestLibraryName<'a> { + suggested_name: &'a str, +} diff --git a/compiler/rustc_metadata/src/diagnostics.rs b/compiler/rustc_metadata/src/diagnostics.rs index 01456377a234f..8fc2b27bde8e4 100644 --- a/compiler/rustc_metadata/src/diagnostics.rs +++ b/compiler/rustc_metadata/src/diagnostics.rs @@ -253,46 +253,6 @@ pub(crate) struct FailedCopyToStdout { )] pub(crate) struct BinaryOutputToTty; -#[derive(Diagnostic)] -#[diag("could not find native static library `{$libname}`, perhaps an -L flag is missing?")] -pub(crate) struct MissingNativeLibrary<'a> { - libname: &'a str, - #[subdiagnostic] - suggest_name: Option>, -} - -impl<'a> MissingNativeLibrary<'a> { - pub(crate) fn new(libname: &'a str, verbatim: bool) -> Self { - // if it looks like the user has provided a complete filename rather just the bare lib name, - // then provide a note that they might want to try trimming the name - let suggested_name = if !verbatim { - if let Some(libname) = libname.strip_circumfix("lib", ".a") { - // this is a unix style filename so trim prefix & suffix - Some(libname) - } else if let Some(libname) = libname.strip_suffix(".lib") { - // this is a Windows style filename so just trim the suffix - Some(libname) - } else { - None - } - } else { - None - }; - - Self { - libname, - suggest_name: suggested_name - .map(|suggested_name| SuggestLibraryName { suggested_name }), - } - } -} - -#[derive(Subdiagnostic)] -#[help("only provide the library name `{$suggested_name}`, not the full filename")] -pub(crate) struct SuggestLibraryName<'a> { - suggested_name: &'a str, -} - #[derive(Diagnostic)] #[diag("couldn't create a temp dir: {$err}")] pub(crate) struct FailedCreateTempdir { diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index 3e9b0c95423b8..306e344c23095 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -27,8 +27,4 @@ pub mod locator; pub use fs::{METADATA_FILENAME, emit_wrapper_file}; pub use host_dylib::{DylibError, load_symbol_from_dylib}; -pub use native_libs::{ - NativeLibSearchFallback, find_native_static_library, try_find_native_dynamic_library, - try_find_native_static_library, walk_native_lib_search_dirs, -}; pub use rmeta::{EncodedMetadata, METADATA_HEADER, ProcMacroKind, encode_metadata, rendered_const}; diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index 4218fe47bd3d1..1c0d8d2affe6a 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -1,6 +1,3 @@ -use std::ops::ControlFlow; -use std::path::{Path, PathBuf}; - use rustc_abi::ExternAbi; use rustc_attr_parsing::eval_config_entry; use rustc_data_structures::fx::FxHashSet; @@ -15,158 +12,12 @@ use rustc_session::Session; use rustc_session::cstore::{ DllCallingConvention, DllImport, DllImportSymbolType, ForeignModule, NativeLib, }; -use rustc_session::search_paths::PathKind; use rustc_span::Symbol; use rustc_span::def_id::{DefId, LOCAL_CRATE}; -use rustc_target::spec::{Arch, BinaryFormat, CfgAbi, Env, LinkSelfContainedComponents, Os}; +use rustc_target::spec::{Arch, BinaryFormat, CfgAbi}; use crate::diagnostics; -/// The fallback directories are passed to linker, but not used when rustc does the search, -/// because in the latter case the set of fallback directories cannot always be determined -/// consistently at the moment. -pub struct NativeLibSearchFallback<'a> { - pub self_contained_components: LinkSelfContainedComponents, - pub apple_sdk_root: Option<&'a Path>, -} - -pub fn walk_native_lib_search_dirs( - sess: &Session, - fallback: Option>, - mut f: impl FnMut(&Path, bool /*is_framework*/) -> ControlFlow, -) -> ControlFlow { - // Library search paths explicitly supplied by user (`-L` on the command line). - for search_path in sess.target_filesearch().cli_search_paths(PathKind::Native) { - f(&search_path.dir, false)?; - } - for search_path in sess.target_filesearch().cli_search_paths(PathKind::Framework) { - // Frameworks are looked up strictly in framework-specific paths. - if search_path.kind != PathKind::All { - f(&search_path.dir, true)?; - } - } - - let Some(NativeLibSearchFallback { self_contained_components, apple_sdk_root }) = fallback - else { - return ControlFlow::Continue(()); - }; - - // The toolchain ships some native library components and self-contained linking was enabled. - // Add the self-contained library directory to search paths. - if self_contained_components.intersects( - LinkSelfContainedComponents::LIBC - | LinkSelfContainedComponents::UNWIND - | LinkSelfContainedComponents::MINGW, - ) { - f(&sess.target_tlib_path.dir.join("self-contained"), false)?; - } - - let has_shared_llvm_apple_darwin = - sess.target.is_like_darwin && sess.target_tlib_path.dir.join("libLLVM.dylib").exists(); - - // Toolchains for some targets may ship `libunwind.a`, but place it into the main sysroot - // library directory instead of the self-contained directories. - // Sanitizer libraries have the same issue and are also linked by name on Apple targets. - // The targets here should be in sync with `copy_third_party_objects` in bootstrap. - // On Apple targets, shared LLVM is linked by name, so when `libLLVM.dylib` is - // present in the target libdir, add that directory to the linker search path. - // FIXME: implement `-Clink-self-contained=+/-unwind,+/-sanitizers`, move the shipped libunwind - // and sanitizers to self-contained directory, and stop adding this search path. - // FIXME: On AIX this also has the side-effect of making the list of library search paths - // non-empty, which is needed or the linker may decide to record the LIBPATH env, if - // defined, as the search path instead of appending the default search paths. - if sess.target.cfg_abi == CfgAbi::Fortanix - || sess.target.os == Os::Linux - || sess.target.os == Os::Fuchsia - || sess.target.is_like_aix - || sess.target.is_like_darwin - && (!sess.sanitizers().is_empty() || has_shared_llvm_apple_darwin) - || sess.target.os == Os::Windows - && sess.target.env == Env::Gnu - && sess.target.cfg_abi == CfgAbi::Llvm - { - f(&sess.target_tlib_path.dir, false)?; - } - - // Mac Catalyst uses the macOS SDK, but to link to iOS-specific frameworks - // we must have the support library stubs in the library search path (#121430). - if let Some(sdk_root) = apple_sdk_root - && sess.target.env == Env::MacAbi - { - f(&sdk_root.join("System/iOSSupport/usr/lib"), false)?; - f(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"), true)?; - } - - ControlFlow::Continue(()) -} - -pub fn try_find_native_static_library( - sess: &Session, - name: &str, - verbatim: bool, -) -> Option { - let default = sess.staticlib_components(verbatim); - let formats = if verbatim { - vec![default] - } else { - // On Windows, static libraries sometimes show up as libfoo.a and other - // times show up as foo.lib - let unix = ("lib", ".a"); - if default == unix { vec![default] } else { vec![default, unix] } - }; - - walk_native_lib_search_dirs(sess, None, |dir, is_framework| { - if !is_framework { - for (prefix, suffix) in &formats { - let test = dir.join(format!("{prefix}{name}{suffix}")); - if test.exists() { - return ControlFlow::Break(test); - } - } - } - ControlFlow::Continue(()) - }) - .break_value() -} - -pub fn try_find_native_dynamic_library( - sess: &Session, - name: &str, - verbatim: bool, -) -> Option { - let default = sess.staticlib_components(verbatim); - let formats = if verbatim { - vec![default] - } else { - // While the official naming convention for MSVC import libraries - // is foo.lib, Meson follows the libfoo.dll.a convention to - // disambiguate .a for static libraries - let meson = ("lib", ".dll.a"); - // and MinGW uses .a altogether - let mingw = ("lib", ".a"); - vec![default, meson, mingw] - }; - - walk_native_lib_search_dirs(sess, None, |dir, is_framework| { - if !is_framework { - for (prefix, suffix) in &formats { - let test = dir.join(format!("{prefix}{name}{suffix}")); - if test.exists() { - return ControlFlow::Break(test); - } - } - } - ControlFlow::Continue(()) - }) - .break_value() -} - -pub fn find_native_static_library(name: &str, verbatim: bool, sess: &Session) -> PathBuf { - try_find_native_static_library(sess, name, verbatim).unwrap_or_else(|| { - sess.dcx().emit_fatal(diagnostics::MissingNativeLibrary::new(name, verbatim)) - }) -} - pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec { let mut collector = Collector { tcx, libs: Vec::new() }; if tcx.sess.opts.unstable_opts.link_directives {