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