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