1use std::env;
2use std::error::Error;
3use std::ffi::OsString;
4use std::fs::{self, File};
5use std::io::{self, BufWriter, Write};
6use std::path::{Path, PathBuf};
7
8use ar_archive_writer::{
9 ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream,
10};
11pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
12use object::read::archive::ArchiveFile;
13use object::read::macho::FatArch;
14use rustc_data_structures::fx::FxIndexSet;
15use rustc_data_structures::memmap::Mmap;
16use rustc_fs_util::TempDirBuilder;
17use rustc_metadata::EncodedMetadata;
18use rustc_session::Session;
19use rustc_span::Symbol;
20use rustc_target::spec::Arch;
21use tracing::trace;
22
23use super::metadata::{create_compressed_metadata_file, search_for_section};
24use crate::common;
25pub use crate::errors::ExtractBundledLibsError;
27use crate::errors::{
28 ArchiveBuildFailure, DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorCreatingImportLibrary,
29 ErrorWritingDEFFile, UnknownArchiveKind,
30};
31
32pub struct ImportLibraryItem {
35 pub name: String,
37 pub ordinal: Option<u16>,
39 pub symbol_name: Option<String>,
41 pub is_data: bool,
43}
44
45impl ImportLibraryItem {
46 fn into_coff_short_export(self, sess: &Session) -> COFFShortExport {
47 let import_name = (sess.target.arch == Arch::Arm64EC).then(|| self.name.clone());
48 COFFShortExport {
49 name: self.name,
50 ext_name: None,
51 symbol_name: self.symbol_name,
52 import_name,
53 export_as: None,
54 ordinal: self.ordinal.unwrap_or(0),
55 noname: self.ordinal.is_some(),
56 data: self.is_data,
57 private: false,
58 constant: false,
59 }
60 }
61}
62
63pub trait ArchiveBuilderBuilder {
64 fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a>;
65
66 fn create_dylib_metadata_wrapper(
67 &self,
68 sess: &Session,
69 metadata: &EncodedMetadata,
70 symbol_name: &str,
71 ) -> Vec<u8> {
72 create_compressed_metadata_file(sess, metadata, symbol_name)
73 }
74
75 fn create_dll_import_lib(
81 &self,
82 sess: &Session,
83 lib_name: &str,
84 items: Vec<ImportLibraryItem>,
85 output_path: &Path,
86 ) {
87 if common::is_mingw_gnu_toolchain(&sess.target) {
88 create_mingw_dll_import_lib(sess, lib_name, items, output_path);
94 } else {
95 trace!("creating import library");
96 trace!(" dll_name {:#?}", lib_name);
97 trace!(" output_path {}", output_path.display());
98 trace!(
99 " import names: {}",
100 items
101 .iter()
102 .map(|ImportLibraryItem { name, .. }| name.clone())
103 .collect::<Vec<_>>()
104 .join(", "),
105 );
106
107 let mut file = match fs::File::create_new(&output_path) {
114 Ok(file) => file,
115 Err(error) => sess
116 .dcx()
117 .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }),
118 };
119
120 let exports =
121 items.into_iter().map(|item| item.into_coff_short_export(sess)).collect::<Vec<_>>();
122 let machine = match &sess.target.arch {
123 Arch::X86_64 => MachineTypes::AMD64,
124 Arch::X86 => MachineTypes::I386,
125 Arch::AArch64 => MachineTypes::ARM64,
126 Arch::Arm64EC => MachineTypes::ARM64EC,
127 Arch::Arm => MachineTypes::ARMNT,
128 cpu => panic!("unsupported cpu type {cpu}"),
129 };
130
131 if let Err(error) = ar_archive_writer::write_import_library(
132 &mut file,
133 lib_name,
134 &exports,
135 machine,
136 !sess.target.is_like_msvc,
137 true,
142 &[],
143 ) {
144 sess.dcx()
145 .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() });
146 }
147 }
148 }
149
150 fn extract_bundled_libs<'a>(
151 &'a self,
152 rlib: &'a Path,
153 outdir: &Path,
154 bundled_lib_file_names: &FxIndexSet<Symbol>,
155 ) -> Result<(), ExtractBundledLibsError<'a>> {
156 let archive_map = unsafe {
157 Mmap::map(
158 File::open(rlib)
159 .map_err(|e| ExtractBundledLibsError::OpenFile { rlib, error: Box::new(e) })?,
160 )
161 .map_err(|e| ExtractBundledLibsError::MmapFile { rlib, error: Box::new(e) })?
162 };
163 let archive = ArchiveFile::parse(&*archive_map)
164 .map_err(|e| ExtractBundledLibsError::ParseArchive { rlib, error: Box::new(e) })?;
165
166 for entry in archive.members() {
167 let entry = entry
168 .map_err(|e| ExtractBundledLibsError::ReadEntry { rlib, error: Box::new(e) })?;
169 let data = entry
170 .data(&*archive_map)
171 .map_err(|e| ExtractBundledLibsError::ArchiveMember { rlib, error: Box::new(e) })?;
172 let name = std::str::from_utf8(entry.name())
173 .map_err(|e| ExtractBundledLibsError::ConvertName { rlib, error: Box::new(e) })?;
174 if !bundled_lib_file_names.contains(&Symbol::intern(name)) {
175 continue; }
177 let data = search_for_section(rlib, data, ".bundled_lib").map_err(|e| {
178 ExtractBundledLibsError::ExtractSection { rlib, error: Box::<dyn Error>::from(e) }
179 })?;
180 std::fs::write(&outdir.join(&name), data)
181 .map_err(|e| ExtractBundledLibsError::WriteFile { rlib, error: Box::new(e) })?;
182 }
183 Ok(())
184 }
185}
186
187fn create_mingw_dll_import_lib(
188 sess: &Session,
189 lib_name: &str,
190 items: Vec<ImportLibraryItem>,
191 output_path: &Path,
192) {
193 let def_file_path = output_path.with_extension("def");
194
195 let def_file_content = format!(
196 "EXPORTS\n{}",
197 items
198 .into_iter()
199 .map(|ImportLibraryItem { name, ordinal, .. }| {
200 match ordinal {
201 Some(n) => format!("{name} @{n} NONAME"),
202 None => name,
203 }
204 })
205 .collect::<Vec<String>>()
206 .join("\n")
207 );
208
209 match std::fs::write(&def_file_path, def_file_content) {
210 Ok(_) => {}
211 Err(e) => {
212 sess.dcx().emit_fatal(ErrorWritingDEFFile { error: e });
213 }
214 };
215
216 let dlltool = find_binutils_dlltool(sess);
220 let temp_prefix = {
221 let mut path = PathBuf::from(&output_path);
222 path.pop();
223 path.push(lib_name);
224 path
225 };
226 let (dlltool_target_arch, dlltool_target_bitness) = match &sess.target.arch {
229 Arch::X86_64 => ("i386:x86-64", "--64"),
230 Arch::X86 => ("i386", "--32"),
231 Arch::AArch64 => ("arm64", "--64"),
232 Arch::Arm => ("arm", "--32"),
233 arch => panic!("unsupported arch {arch}"),
234 };
235 let mut dlltool_cmd = std::process::Command::new(&dlltool);
236 dlltool_cmd
237 .arg("-d")
238 .arg(def_file_path)
239 .arg("-D")
240 .arg(lib_name)
241 .arg("-l")
242 .arg(&output_path)
243 .arg("-m")
244 .arg(dlltool_target_arch)
245 .arg("-f")
246 .arg(dlltool_target_bitness)
247 .arg("--no-leading-underscore")
248 .arg("--temp-prefix")
249 .arg(temp_prefix);
250
251 match dlltool_cmd.output() {
252 Err(e) => {
253 sess.dcx().emit_fatal(ErrorCallingDllTool {
254 dlltool_path: dlltool.to_string_lossy(),
255 error: e,
256 });
257 }
258 Ok(output) if !output.stderr.is_empty() => {
260 sess.dcx().emit_fatal(DlltoolFailImportLibrary {
261 dlltool_path: dlltool.to_string_lossy(),
262 dlltool_args: dlltool_cmd
263 .get_args()
264 .map(|arg| arg.to_string_lossy())
265 .collect::<Vec<_>>()
266 .join(" "),
267 stdout: String::from_utf8_lossy(&output.stdout),
268 stderr: String::from_utf8_lossy(&output.stderr),
269 })
270 }
271 _ => {}
272 }
273}
274
275fn find_binutils_dlltool(sess: &Session) -> OsString {
276 assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc);
277 if let Some(dlltool_path) = &sess.opts.cg.dlltool {
278 return dlltool_path.clone().into_os_string();
279 }
280
281 let tool_name: OsString = if sess.host.options.is_like_windows {
282 "dlltool.exe"
284 } else {
285 match sess.target.arch {
287 Arch::X86_64 => "x86_64-w64-mingw32-dlltool",
288 Arch::X86 => "i686-w64-mingw32-dlltool",
289 Arch::AArch64 => "aarch64-w64-mingw32-dlltool",
290
291 _ => "dlltool",
293 }
294 }
295 .into();
296
297 for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
299 let full_path = dir.join(&tool_name);
300 if full_path.is_file() {
301 return full_path.into_os_string();
302 }
303 }
304
305 tool_name
309}
310
311pub trait ArchiveBuilder {
312 fn add_file(&mut self, path: &Path);
313
314 fn add_archive(
315 &mut self,
316 archive: &Path,
317 skip: Box<dyn FnMut(&str) -> bool + 'static>,
318 ) -> io::Result<()>;
319
320 fn build(self: Box<Self>, output: &Path) -> bool;
321}
322
323pub struct ArArchiveBuilderBuilder;
324
325impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder {
326 fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a> {
327 Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER))
328 }
329}
330
331#[must_use = "must call build() to finish building the archive"]
332pub struct ArArchiveBuilder<'a> {
333 sess: &'a Session,
334 object_reader: &'static ObjectReader,
335
336 src_archives: Vec<(PathBuf, Mmap)>,
337 entries: Vec<(Vec<u8>, ArchiveEntry)>,
340}
341
342#[derive(Debug)]
343enum ArchiveEntry {
344 FromArchive { archive_index: usize, file_range: (u64, u64) },
345 File(PathBuf),
346}
347
348impl<'a> ArArchiveBuilder<'a> {
349 pub fn new(sess: &'a Session, object_reader: &'static ObjectReader) -> ArArchiveBuilder<'a> {
350 ArArchiveBuilder { sess, object_reader, src_archives: vec![], entries: vec![] }
351 }
352}
353
354fn try_filter_fat_archs(
355 archs: &[impl FatArch],
356 target_arch: object::Architecture,
357 archive_path: &Path,
358 archive_map_data: &[u8],
359) -> io::Result<Option<PathBuf>> {
360 let desired = match archs.iter().find(|a| a.architecture() == target_arch) {
361 Some(a) => a,
362 None => return Ok(None),
363 };
364
365 let (mut new_f, extracted_path) = tempfile::Builder::new()
366 .suffix(archive_path.file_name().unwrap())
367 .tempfile()?
368 .keep()
369 .unwrap();
370
371 new_f.write_all(
372 desired.data(archive_map_data).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?,
373 )?;
374
375 Ok(Some(extracted_path))
376}
377
378pub fn try_extract_macho_fat_archive(
379 sess: &Session,
380 archive_path: &Path,
381) -> io::Result<Option<PathBuf>> {
382 let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
383 let target_arch = match sess.target.arch {
384 Arch::AArch64 => object::Architecture::Aarch64,
385 Arch::X86_64 => object::Architecture::X86_64,
386 _ => return Ok(None),
387 };
388
389 if let Ok(h) = object::read::macho::MachOFatFile32::parse(&*archive_map) {
390 let archs = h.arches();
391 try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
392 } else if let Ok(h) = object::read::macho::MachOFatFile64::parse(&*archive_map) {
393 let archs = h.arches();
394 try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
395 } else {
396 Ok(None)
398 }
399}
400
401impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
402 fn add_archive(
403 &mut self,
404 archive_path: &Path,
405 mut skip: Box<dyn FnMut(&str) -> bool + 'static>,
406 ) -> io::Result<()> {
407 let mut archive_path = archive_path.to_path_buf();
408 if self.sess.target.llvm_target.contains("-apple-macosx")
409 && let Some(new_archive_path) = try_extract_macho_fat_archive(self.sess, &archive_path)?
410 {
411 archive_path = new_archive_path
412 }
413
414 if self.src_archives.iter().any(|archive| archive.0 == archive_path) {
415 return Ok(());
416 }
417
418 let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
419 let archive = ArchiveFile::parse(&*archive_map)
420 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
421 let archive_index = self.src_archives.len();
422
423 for entry in archive.members() {
424 let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
425 let file_name = String::from_utf8(entry.name().to_vec())
426 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
427 if !skip(&file_name) {
428 if entry.is_thin() {
429 let member_path = archive_path.parent().unwrap().join(Path::new(&file_name));
430 self.entries.push((file_name.into_bytes(), ArchiveEntry::File(member_path)));
431 } else {
432 self.entries.push((
433 file_name.into_bytes(),
434 ArchiveEntry::FromArchive { archive_index, file_range: entry.file_range() },
435 ));
436 }
437 }
438 }
439
440 self.src_archives.push((archive_path, archive_map));
441 Ok(())
442 }
443
444 fn add_file(&mut self, file: &Path) {
446 self.entries.push((
447 file.file_name().unwrap().to_str().unwrap().to_string().into_bytes(),
448 ArchiveEntry::File(file.to_owned()),
449 ));
450 }
451
452 fn build(self: Box<Self>, output: &Path) -> bool {
455 let sess = self.sess;
456 match self.build_inner(output) {
457 Ok(any_members) => any_members,
458 Err(error) => {
459 sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
460 }
461 }
462 }
463}
464
465impl<'a> ArArchiveBuilder<'a> {
466 fn build_inner(self, output: &Path) -> io::Result<bool> {
467 let archive_kind = match &*self.sess.target.archive_format {
468 "gnu" => ArchiveKind::Gnu,
469 "bsd" => ArchiveKind::Bsd,
470 "darwin" => ArchiveKind::Darwin,
471 "coff" => ArchiveKind::Coff,
472 "aix_big" => ArchiveKind::AixBig,
473 kind => {
474 self.sess.dcx().emit_fatal(UnknownArchiveKind { kind });
475 }
476 };
477
478 let mut entries = Vec::new();
479
480 for (entry_name, entry) in self.entries {
481 let data =
482 match entry {
483 ArchiveEntry::FromArchive { archive_index, file_range } => {
484 let src_archive = &self.src_archives[archive_index];
485
486 let data = &src_archive.1
487 [file_range.0 as usize..file_range.0 as usize + file_range.1 as usize];
488
489 Box::new(data) as Box<dyn AsRef<[u8]>>
490 }
491 ArchiveEntry::File(file) => unsafe {
492 Box::new(
493 Mmap::map(File::open(file).map_err(|err| {
494 io_error_context("failed to open object file", err)
495 })?)
496 .map_err(|err| io_error_context("failed to map object file", err))?,
497 ) as Box<dyn AsRef<[u8]>>
498 },
499 };
500
501 entries.push(NewArchiveMember {
502 buf: data,
503 object_reader: self.object_reader,
504 member_name: String::from_utf8(entry_name).unwrap(),
505 mtime: 0,
506 uid: 0,
507 gid: 0,
508 perms: 0o644,
509 })
510 }
511
512 let archive_tmpdir = TempDirBuilder::new()
521 .suffix(".temp-archive")
522 .tempdir_in(output.parent().unwrap_or_else(|| Path::new("")))
523 .map_err(|err| {
524 io_error_context("couldn't create a directory for the temp file", err)
525 })?;
526 let archive_tmpfile_path = archive_tmpdir.path().join("tmp.a");
527 let archive_tmpfile = File::create_new(&archive_tmpfile_path)
528 .map_err(|err| io_error_context("couldn't create the temp file", err))?;
529
530 let mut archive_tmpfile = BufWriter::new(archive_tmpfile);
531 write_archive_to_stream(
532 &mut archive_tmpfile,
533 &entries,
534 archive_kind,
535 false,
536 Some(self.sess.target.arch == Arch::Arm64EC),
537 )?;
538 archive_tmpfile.flush()?;
539 drop(archive_tmpfile);
540
541 let any_entries = !entries.is_empty();
542 drop(entries);
543 drop(self.src_archives);
546
547 fs::rename(archive_tmpfile_path, output)
548 .map_err(|err| io_error_context("failed to rename archive file", err))?;
549 archive_tmpdir
550 .close()
551 .map_err(|err| io_error_context("failed to remove temporary directory", err))?;
552
553 Ok(any_entries)
554 }
555}
556
557fn io_error_context(context: &str, err: io::Error) -> io::Error {
558 io::Error::new(io::ErrorKind::Other, format!("{context}: {err}"))
559}