From db94aa12653ad166592beb6fdbffc6cd8e65275a Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:49:58 +0000 Subject: [PATCH 1/6] Simplify find_commandline_library --- compiler/rustc_metadata/src/locator.rs | 44 ++++++++++---------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 3f3e58384cbf4..70c43e647fa64 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -728,37 +728,25 @@ impl<'a> CrateLocator<'a> { let Some(file) = loc_orig.file_name().and_then(|s| s.to_str()) else { return Err(CrateError::ExternLocationNotFile(self.crate_name, loc_orig.clone())); }; - // FnMut cannot return reference to captured value, so references - // must be taken outside the closure. - let rlibs = &mut rlibs; - let rmetas = &mut rmetas; - let dylibs = &mut dylibs; - let type_via_filename = (|| { - if file.starts_with("lib") { - if file.ends_with(".rlib") { - return Some(rlibs); - } - if file.ends_with(".rmeta") { - return Some(rmetas); - } - } - let dll_prefix = self.target.dll_prefix.as_ref(); - let dll_suffix = self.target.dll_suffix.as_ref(); - if file.starts_with(dll_prefix) && file.ends_with(dll_suffix) { - return Some(dylibs); - } - None - })(); - match type_via_filename { - Some(type_via_filename) => { - type_via_filename.insert(loc_canon.clone(), PathKind::ExternFlag); + if file.starts_with("lib") { + if file.ends_with(".rlib") { + rlibs.insert(loc_canon.clone(), PathKind::ExternFlag); + continue; } - None => { - self.crate_rejections - .via_filename - .push(CrateMismatch { path: loc_orig.clone(), got: String::new() }); + if file.ends_with(".rmeta") { + rmetas.insert(loc_canon.clone(), PathKind::ExternFlag); + continue; } } + let dll_prefix = self.target.dll_prefix.as_ref(); + let dll_suffix = self.target.dll_suffix.as_ref(); + if file.starts_with(dll_prefix) && file.ends_with(dll_suffix) { + dylibs.insert(loc_canon.clone(), PathKind::ExternFlag); + continue; + } + self.crate_rejections + .via_filename + .push(CrateMismatch { path: loc_orig.clone(), got: String::new() }); } // Extract the dylib/rlib/rmeta triple. From 956c40672f5c7cafdfa6349eb3dba98cd01c2097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Feb 2025 21:23:10 +0100 Subject: [PATCH 2/6] Add `-Zsplit-metadata` CLI option --- compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_session/src/options.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index aabd235bcabed..dd6a89916019e 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -859,6 +859,7 @@ fn test_unstable_options_tracking_hash() { tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc"))); tracked!(small_data_threshold, Some(16)); tracked!(split_lto_unit, Some(true)); + tracked!(split_metadata, true); tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); tracked!(stack_protector, StackProtector::All); tracked!(teach, true); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 8977365ee73d9..283a193350ce0 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2482,6 +2482,8 @@ written to standard error output)"), by the linker"), split_lto_unit: Option = (None, parse_opt_bool, [TRACKED], "enable LTO unit splitting (default: no)"), + split_metadata: bool = (false, parse_bool, [TRACKED], + "split metadata out of libraries into .rmeta files"), src_hash_algorithm: Option = (None, parse_src_file_hash, [TRACKED], "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), #[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")] From 57c4a523d4f0e53c7d72f1a605c1787251ab91f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Feb 2025 21:34:11 +0100 Subject: [PATCH 3/6] Add documentation of the option into the unstable book --- src/doc/unstable-book/src/compiler-flags/split_metadata.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/doc/unstable-book/src/compiler-flags/split_metadata.md diff --git a/src/doc/unstable-book/src/compiler-flags/split_metadata.md b/src/doc/unstable-book/src/compiler-flags/split_metadata.md new file mode 100644 index 0000000000000..2fce675c63a4a --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/split_metadata.md @@ -0,0 +1,3 @@ +## `split-metadata` + +This option instructs `rustc` to only include a stub metadata section in `rlib` and `dylib` crate types instead of full metadata, to reduce their size on disk. You will probably want to combine this option with `--emit=metadata` to produce the full metadata into a separate `.rmeta` file. From 0fef891fa8766af3e24360d71622a3162e149ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Feb 2025 21:45:13 +0100 Subject: [PATCH 4/6] Store only a metadata stub into `rlibs` and `dylibs` with `-Zsplit-metadata` --- compiler/rustc_codegen_ssa/src/back/link.rs | 2 +- .../rustc_codegen_ssa/src/back/metadata.rs | 4 +- compiler/rustc_metadata/src/fs.rs | 26 ++++-- compiler/rustc_metadata/src/rmeta/encoder.rs | 91 +++++++++++++++---- compiler/rustc_metadata/src/rmeta/mod.rs | 6 ++ 5 files changed, 101 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 4c07645026943..faf8554dc91a6 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -296,7 +296,7 @@ fn link_rlib<'a>( let (metadata, metadata_position) = create_wrapper_file( sess, ".rmeta".to_string(), - codegen_results.metadata.raw_data(), + codegen_results.metadata.stub_or_full(), ); let metadata = emit_wrapper_file(sess, &metadata, tmpdir, METADATA_FILENAME); match metadata_position { diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 236507ac0cd28..bfea5209a633b 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -570,8 +570,8 @@ pub fn create_compressed_metadata_file( symbol_name: &str, ) -> Vec { let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec(); - packed_metadata.write_all(&(metadata.raw_data().len() as u64).to_le_bytes()).unwrap(); - packed_metadata.extend(metadata.raw_data()); + packed_metadata.write_all(&(metadata.stub_or_full().len() as u64).to_le_bytes()).unwrap(); + packed_metadata.extend(metadata.stub_or_full()); let Some(mut file) = create_object_file(sess) else { if sess.target.is_like_wasm { diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 4450d050c8e1f..1a715384105cc 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -3,7 +3,7 @@ use std::{fs, io}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_middle::ty::TyCtxt; -use rustc_session::config::{OutFileName, OutputType}; +use rustc_session::config::{CrateType, OutFileName, OutputType}; use rustc_session::output::filename_for_metadata; use rustc_session::{MetadataKind, Session}; use tempfile::Builder as TempFileBuilder; @@ -50,7 +50,14 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { .tempdir_in(out_filename.parent().unwrap_or_else(|| Path::new(""))) .unwrap_or_else(|err| tcx.dcx().emit_fatal(FailedCreateTempdir { err })); let metadata_tmpdir = MaybeTempDir::new(metadata_tmpdir, tcx.sess.opts.cg.save_temps); - let metadata_filename = metadata_tmpdir.as_ref().join(METADATA_FILENAME); + let metadata_filename = metadata_tmpdir.as_ref().join("full.rmeta"); + let metadata_stub_filename = if tcx.sess.opts.unstable_opts.split_metadata + && !tcx.crate_types().contains(&CrateType::ProcMacro) + { + Some(metadata_tmpdir.as_ref().join("stub.rmeta")) + } else { + None + }; // Always create a file at `metadata_filename`, even if we have nothing to write to it. // This simplifies the creation of the output `out_filename` when requested. @@ -60,9 +67,15 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { std::fs::File::create(&metadata_filename).unwrap_or_else(|err| { tcx.dcx().emit_fatal(FailedCreateFile { filename: &metadata_filename, err }); }); + if let Some(metadata_stub_filename) = &metadata_stub_filename { + std::fs::File::create(metadata_stub_filename).unwrap_or_else(|err| { + tcx.dcx() + .emit_fatal(FailedCreateFile { filename: &metadata_stub_filename, err }); + }); + } } MetadataKind::Uncompressed | MetadataKind::Compressed => { - encode_metadata(tcx, &metadata_filename); + encode_metadata(tcx, &metadata_filename, metadata_stub_filename.as_deref()) } }; @@ -100,9 +113,10 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { // Load metadata back to memory: codegen may need to include it in object files. let metadata = - EncodedMetadata::from_path(metadata_filename, metadata_tmpdir).unwrap_or_else(|err| { - tcx.dcx().emit_fatal(FailedCreateEncodedMetadata { err }); - }); + EncodedMetadata::from_path(metadata_filename, metadata_stub_filename, metadata_tmpdir) + .unwrap_or_else(|err| { + tcx.dcx().emit_fatal(FailedCreateEncodedMetadata { err }); + }); let need_metadata_module = metadata_kind == MetadataKind::Compressed; diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 88a88847e6b8b..9946b9d2c7964 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -703,6 +703,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { triple: tcx.sess.opts.target_triple.clone(), hash: tcx.crate_hash(LOCAL_CRATE), is_proc_macro_crate: proc_macro_data.is_some(), + is_stub: false, }, extra_filename: tcx.sess.opts.cg.extra_filename.clone(), stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(), @@ -2235,8 +2236,12 @@ fn prefetch_mir(tcx: TyCtxt<'_>) { // generated regardless of trailing bytes that end up in it. pub struct EncodedMetadata { - // The declaration order matters because `mmap` should be dropped before `_temp_dir`. - mmap: Option, + // The declaration order matters because `full_metadata` should be dropped + // before `_temp_dir`. + full_metadata: Option, + // This is an optional stub metadata containing only the crate header. + // The header should be very small, so we load it directly into memory. + stub_metadata: Option>, // We need to carry MaybeTempDir to avoid deleting the temporary // directory while accessing the Mmap. _temp_dir: Option, @@ -2244,33 +2249,50 @@ pub struct EncodedMetadata { impl EncodedMetadata { #[inline] - pub fn from_path(path: PathBuf, temp_dir: Option) -> std::io::Result { + pub fn from_path( + path: PathBuf, + stub_path: Option, + temp_dir: Option, + ) -> std::io::Result { let file = std::fs::File::open(&path)?; let file_metadata = file.metadata()?; if file_metadata.len() == 0 { - return Ok(Self { mmap: None, _temp_dir: None }); + return Ok(Self { full_metadata: None, stub_metadata: None, _temp_dir: None }); } - let mmap = unsafe { Some(Mmap::map(file)?) }; - Ok(Self { mmap, _temp_dir: temp_dir }) + let full_mmap = unsafe { Some(Mmap::map(file)?) }; + + let stub = + if let Some(stub_path) = stub_path { Some(std::fs::read(stub_path)?) } else { None }; + + Ok(Self { full_metadata: full_mmap, stub_metadata: stub, _temp_dir: temp_dir }) + } + + #[inline] + pub fn full(&self) -> &[u8] { + &self.full_metadata.as_deref().unwrap_or_default() } #[inline] - pub fn raw_data(&self) -> &[u8] { - self.mmap.as_deref().unwrap_or_default() + pub fn stub_or_full(&self) -> &[u8] { + self.stub_metadata.as_deref().unwrap_or(self.full()) } } impl Encodable for EncodedMetadata { fn encode(&self, s: &mut S) { - let slice = self.raw_data(); + self.stub_metadata.encode(s); + + let slice = self.full(); slice.encode(s) } } impl Decodable for EncodedMetadata { fn decode(d: &mut D) -> Self { + let stub = >>::decode(d); + let len = d.read_usize(); - let mmap = if len > 0 { + let full_metadata = if len > 0 { let mut mmap = MmapMut::map_anon(len).unwrap(); mmap.copy_from_slice(d.read_raw_bytes(len)); Some(mmap.make_read_only().unwrap()) @@ -2278,11 +2300,11 @@ impl Decodable for EncodedMetadata { None }; - Self { mmap, _temp_dir: None } + Self { full_metadata, stub_metadata: stub, _temp_dir: None } } } -pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path) { +pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { let _prof_timer = tcx.prof.verbose_generic_activity("generate_crate_metadata"); // Since encoding metadata is not in a query, and nothing is cached, @@ -2296,6 +2318,42 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path) { join(|| prefetch_mir(tcx), || tcx.exported_symbols(LOCAL_CRATE)); } + with_encode_metadata_header(tcx, path, |ecx| { + // Encode all the entries and extra information in the crate, + // culminating in the `CrateRoot` which points to all of it. + let root = ecx.encode_crate_root(); + + // Flush buffer to ensure backing file has the correct size. + ecx.opaque.flush(); + // Record metadata size for self-profiling + tcx.prof.artifact_size( + "crate_metadata", + "crate_metadata", + ecx.opaque.file().metadata().unwrap().len(), + ); + + root.position.get() + }); + + if let Some(ref_path) = ref_path { + with_encode_metadata_header(tcx, ref_path, |ecx| { + let header: LazyValue = ecx.lazy(CrateHeader { + name: tcx.crate_name(LOCAL_CRATE), + triple: tcx.sess.opts.target_triple.clone(), + hash: tcx.crate_hash(LOCAL_CRATE), + is_proc_macro_crate: false, + is_stub: true, + }); + header.position.get() + }); + } +} + +fn with_encode_metadata_header( + tcx: TyCtxt<'_>, + path: &Path, + f: impl FnOnce(&mut EncodeContext<'_, '_>) -> usize, +) { let mut encoder = opaque::FileEncoder::new(path) .unwrap_or_else(|err| tcx.dcx().emit_fatal(FailCreateFileEncoder { err })); encoder.emit_raw_bytes(METADATA_HEADER); @@ -2330,9 +2388,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path) { // Encode the rustc version string in a predictable location. rustc_version(tcx.sess.cfg_version).encode(&mut ecx); - // Encode all the entries and extra information in the crate, - // culminating in the `CrateRoot` which points to all of it. - let root = ecx.encode_crate_root(); + let root_position = f(&mut ecx); // Make sure we report any errors from writing to the file. // If we forget this, compilation can succeed with an incomplete rmeta file, @@ -2342,12 +2398,9 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path) { } let file = ecx.opaque.file(); - if let Err(err) = encode_root_position(file, root.position.get()) { + if let Err(err) = encode_root_position(file, root_position) { tcx.dcx().emit_fatal(FailWriteFile { path: ecx.opaque.path(), err }); } - - // Record metadata size for self-profiling - tcx.prof.artifact_size("crate_metadata", "crate_metadata", file.metadata().unwrap().len()); } fn encode_root_position(mut file: &File, pos: usize) -> Result<(), std::io::Error> { diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 7b34e605c5307..a3bcb4ce6a013 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -220,6 +220,12 @@ pub(crate) struct CrateHeader { /// This is separate from [`ProcMacroData`] to avoid having to update [`METADATA_VERSION`] every /// time ProcMacroData changes. pub(crate) is_proc_macro_crate: bool, + /// Whether this crate metadata section is just a stub. + /// Stubs do not contain the full metadata (it will be typically stored + /// in a separate rmeta file). + /// + /// This is used inside rlibs and dylibs when using `-Zsplit-metadata`. + pub(crate) is_stub: bool, } /// Serialized `.rmeta` data for a crate. From f47345e545656c1a5fa4c671f3728c4fff527afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 24 Feb 2025 11:59:39 +0100 Subject: [PATCH 5/6] Alter lookup of crates --- compiler/rustc_metadata/src/locator.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 70c43e647fa64..00640718916cf 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -247,6 +247,7 @@ pub(crate) struct CrateLocator<'a> { // Immutable per-search configuration. crate_name: Symbol, + // Dependency paths passed through --extern exact_paths: Vec, pub hash: Option, extra_filename: Option<&'a str>, @@ -511,6 +512,8 @@ impl<'a> CrateLocator<'a> { rlib: self.extract_one(rlibs, CrateFlavor::Rlib, &mut slot)?, dylib: self.extract_one(dylibs, CrateFlavor::Dylib, &mut slot)?, }; + // Question: check if we have no rmeta, but rlib/dylib with is_stub, and in that case + // invoke `find_library`? Ok(slot.map(|(svh, metadata, _)| (svh, Library { source, metadata }))) } @@ -730,6 +733,14 @@ impl<'a> CrateLocator<'a> { }; if file.starts_with("lib") { if file.ends_with(".rlib") { + // In case the .rlib contains only stub metadata, we will most likely + // need to load a corresponding .rmeta file. Since it will often be + // located right next to the .rlib path, directly add it to the candidate + // paths as a "fast-path". + let possible_rmeta_path = loc_orig.with_extension("rmeta"); + if let Ok(rmeta_path) = try_canonicalize(possible_rmeta_path) { + rmetas.insert(rmeta_path, PathKind::ExternFlag); + } rlibs.insert(loc_canon.clone(), PathKind::ExternFlag); continue; } @@ -740,7 +751,15 @@ impl<'a> CrateLocator<'a> { } let dll_prefix = self.target.dll_prefix.as_ref(); let dll_suffix = self.target.dll_suffix.as_ref(); - if file.starts_with(dll_prefix) && file.ends_with(dll_suffix) { + if let Some(stem) = + file.strip_prefix(dll_prefix).and_then(|name| name.strip_suffix(dll_suffix)) + { + // See comment above about stub metadata, it applies also here for dylibs + let possible_rmeta_path = + loc_orig.parent().unwrap().join(format!("lib{stem}.rmeta")); + if let Ok(rmeta_path) = try_canonicalize(possible_rmeta_path) { + rmetas.insert(rmeta_path, PathKind::ExternFlag); + } dylibs.insert(loc_canon.clone(), PathKind::ExternFlag); continue; } From bf5a7c2bc04b7514df4db13a8817c4bde7cd412e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 24 Feb 2025 11:59:42 +0100 Subject: [PATCH 6/6] Add tests --- tests/run-make/split-metadata/dep1.rs | 1 + tests/run-make/split-metadata/foo.rs | 5 ++ tests/run-make/split-metadata/rmake.rs | 102 +++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 tests/run-make/split-metadata/dep1.rs create mode 100644 tests/run-make/split-metadata/foo.rs create mode 100644 tests/run-make/split-metadata/rmake.rs diff --git a/tests/run-make/split-metadata/dep1.rs b/tests/run-make/split-metadata/dep1.rs new file mode 100644 index 0000000000000..be70c3933e098 --- /dev/null +++ b/tests/run-make/split-metadata/dep1.rs @@ -0,0 +1 @@ +pub fn func_dep1() {} diff --git a/tests/run-make/split-metadata/foo.rs b/tests/run-make/split-metadata/foo.rs new file mode 100644 index 0000000000000..0cc9cede86082 --- /dev/null +++ b/tests/run-make/split-metadata/foo.rs @@ -0,0 +1,5 @@ +extern crate dep1; + +fn main() { + dep1::func_dep1(); +} diff --git a/tests/run-make/split-metadata/rmake.rs b/tests/run-make/split-metadata/rmake.rs new file mode 100644 index 0000000000000..4643b5364c637 --- /dev/null +++ b/tests/run-make/split-metadata/rmake.rs @@ -0,0 +1,102 @@ +use run_make_support::rfs::{create_dir, remove_file, rename}; +use run_make_support::{Rustc, dynamic_lib_name, run_in_tmpdir, rust_lib_name, rustc}; + +#[derive(Debug, Copy, Clone)] +enum LibraryKind { + Rlib, + Dylib, +} + +impl LibraryKind { + fn crate_type(&self) -> &str { + match self { + LibraryKind::Rlib => "rlib", + LibraryKind::Dylib => "dylib", + } + } + + fn add_extern(&self, rustc: &mut Rustc, dep_name: &str, dep_path: &str) { + let dep_path = match self { + LibraryKind::Dylib => format!("{dep_path}/{}", dynamic_lib_name(dep_name)), + LibraryKind::Rlib => format!("{dep_path}/{}", rust_lib_name(dep_name)), + }; + rustc.extern_(dep_name, dep_path); + } +} + +fn main() { + // The compiler takes different paths based on if --extern is passed or not, so we test all + // combinations (`rlib`/`dylib` x `--extern`/`no --extern`). + for kind in [LibraryKind::Rlib, LibraryKind::Dylib] { + eprintln!("Testing library kind {kind:?}"); + lookup_rmeta_in_lib_dir(kind); + lookup_rmeta_in_lib_search_path(kind); + lookup_rmeta_in_lib_dir_extern(kind); + lookup_rmeta_in_lib_search_path_extern(kind); + } +} + +// Lookup .rmeta file in the same directory as a rlib/dylib with stub metadata. +fn lookup_rmeta_in_lib_dir(kind: LibraryKind) { + run_in_tmpdir(|| { + build_dep_rustc(kind).run(); + rustc().input("foo.rs").run(); + }); +} + +// Lookup .rmeta file in a directory passed to -L. +fn lookup_rmeta_in_lib_search_path(kind: LibraryKind) { + run_in_tmpdir(|| { + build_dep_rustc(kind).run(); + + // Move the rmeta directory to a different path + create_dir("rmeta-dir"); + rename("libdep1.rmeta", "rmeta-dir/libdep1.rmeta"); + + rustc().input("foo.rs").library_search_path("rmeta-dir").run(); + }); +} + +// Lookup .rmeta file in the same directory as a rlib/dylib with stub metadata when specifying the +// dependency using --extern. +fn lookup_rmeta_in_lib_dir_extern(kind: LibraryKind) { + run_in_tmpdir(|| { + // Generate libdep1.rlib and libdep1.rmeta in deps + create_dir("deps"); + build_dep_rustc(kind).out_dir("deps").run(); + + let mut rustc = rustc(); + kind.add_extern(&mut rustc, "dep1", "deps"); + rustc.input("foo.rs").run(); + }); +} + +// Lookup .rmeta file in a directory passed to -L when specifying the dependency using --extern. +fn lookup_rmeta_in_lib_search_path_extern(kind: LibraryKind) { + run_in_tmpdir(|| { + create_dir("deps"); + build_dep_rustc(kind).out_dir("deps").run(); + + // Move the rmeta directory to a different dir + create_dir("rmeta-dir"); + rename("deps/libdep1.rmeta", "rmeta-dir/libdep1.rmeta"); + + let mut rustc = rustc(); + kind.add_extern(&mut rustc, "dep1", "deps"); + rustc.library_search_path("rmeta-dir"); + rustc.input("foo.rs").run(); + }); +} + +fn build_dep_rustc(kind: LibraryKind) -> Rustc { + let mut dep_rustc = rustc(); + dep_rustc + .arg("-Zsplit-metadata") + .crate_type(kind.crate_type()) + .input("dep1.rs") + .emit("metadata,link"); + if matches!(kind, LibraryKind::Dylib) { + dep_rustc.arg("-Cprefer-dynamic"); + } + dep_rustc +}