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