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