Skip to main content

rustc_codegen_ssa/back/
archive.rs

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, ArchiveKind as ObjectArchiveKind};
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 super::rmeta_link::{self, RmetaLink};
25use crate::common;
26// Public for ArchiveBuilderBuilder::extract_bundled_libs
27pub use crate::errors::ExtractBundledLibsError;
28use crate::errors::{
29    ArchiveBuildFailure, DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorCreatingImportLibrary,
30    ErrorWritingDEFFile, UnknownArchiveKind,
31};
32
33/// An item to be included in an import library.
34/// This is a slimmed down version of `COFFShortExport` from `ar-archive-writer`.
35pub struct ImportLibraryItem {
36    /// The name to be exported.
37    pub name: String,
38    /// The ordinal to be exported, if any.
39    pub ordinal: Option<u16>,
40    /// The original, decorated name if `name` is not decorated.
41    pub symbol_name: Option<String>,
42    /// True if this is a data export, false if it is a function export.
43    pub is_data: bool,
44}
45
46impl ImportLibraryItem {
47    fn into_coff_short_export(self, sess: &Session) -> COFFShortExport {
48        let import_name = (sess.target.arch == Arch::Arm64EC).then(|| self.name.clone());
49        COFFShortExport {
50            name: self.name,
51            ext_name: None,
52            symbol_name: self.symbol_name,
53            import_name,
54            export_as: None,
55            ordinal: self.ordinal.unwrap_or(0),
56            noname: self.ordinal.is_some(),
57            data: self.is_data,
58            private: false,
59            constant: false,
60        }
61    }
62}
63
64pub trait ArchiveBuilderBuilder {
65    fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a>;
66
67    fn create_dylib_metadata_wrapper(
68        &self,
69        sess: &Session,
70        metadata: &EncodedMetadata,
71        symbol_name: &str,
72    ) -> Vec<u8> {
73        create_compressed_metadata_file(sess, metadata, symbol_name)
74    }
75
76    /// Creates a DLL Import Library <https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-creation#creating-an-import-library>.
77    /// and returns the path on disk to that import library.
78    /// This functions doesn't take `self` so that it can be called from
79    /// `linker_with_args`, which is specialized on `ArchiveBuilder` but
80    /// doesn't take or create an instance of that type.
81    fn create_dll_import_lib(
82        &self,
83        sess: &Session,
84        lib_name: &str,
85        items: Vec<ImportLibraryItem>,
86        output_path: &Path,
87    ) {
88        if common::is_mingw_gnu_toolchain(&sess.target) {
89            // The binutils linker used on -windows-gnu targets cannot read the import
90            // libraries generated by LLVM: in our attempts, the linker produced an .EXE
91            // that loaded but crashed with an AV upon calling one of the imported
92            // functions. Therefore, use binutils to create the import library instead,
93            // by writing a .DEF file to the temp dir and calling binutils's dlltool.
94            create_mingw_dll_import_lib(sess, lib_name, items, output_path);
95        } else {
96            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/archive.rs:96",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(96u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("creating import library")
                                            as &dyn Value))])
            });
    } else { ; }
};trace!("creating import library");
97            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/archive.rs:97",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(97u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("  dll_name {0:#?}",
                                                    lib_name) as &dyn Value))])
            });
    } else { ; }
};trace!("  dll_name {:#?}", lib_name);
98            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/archive.rs:98",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(98u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("  output_path {0}",
                                                    output_path.display()) as &dyn Value))])
            });
    } else { ; }
};trace!("  output_path {}", output_path.display());
99            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/archive.rs:99",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(99u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("  import names: {0}",
                                                    items.iter().map(|ImportLibraryItem { name, .. }|
                                                                    name.clone()).collect::<Vec<_>>().join(", ")) as
                                            &dyn Value))])
            });
    } else { ; }
};trace!(
100                "  import names: {}",
101                items
102                    .iter()
103                    .map(|ImportLibraryItem { name, .. }| name.clone())
104                    .collect::<Vec<_>>()
105                    .join(", "),
106            );
107
108            // All import names are Rust identifiers and therefore cannot contain \0 characters.
109            // FIXME: when support for #[link_name] is implemented, ensure that the import names
110            // still don't contain any \0 characters. Also need to check that the names don't
111            // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
112            // in definition files.
113
114            let mut file = match fs::File::create_new(&output_path) {
115                Ok(file) => file,
116                Err(error) => sess
117                    .dcx()
118                    .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }),
119            };
120
121            let exports =
122                items.into_iter().map(|item| item.into_coff_short_export(sess)).collect::<Vec<_>>();
123            let machine = match &sess.target.arch {
124                Arch::X86_64 => MachineTypes::AMD64,
125                Arch::X86 => MachineTypes::I386,
126                Arch::AArch64 => MachineTypes::ARM64,
127                Arch::Arm64EC => MachineTypes::ARM64EC,
128                Arch::Arm => MachineTypes::ARMNT,
129                cpu => {
    ::core::panicking::panic_fmt(format_args!("unsupported cpu type {0}",
            cpu));
}panic!("unsupported cpu type {cpu}"),
130            };
131
132            if let Err(error) = ar_archive_writer::write_import_library(
133                &mut file,
134                lib_name,
135                &exports,
136                machine,
137                !sess.target.is_like_msvc,
138                // Enable compatibility with MSVC's `/WHOLEARCHIVE` flag.
139                // Without this flag a duplicate symbol error would be emitted
140                // when linking a rust staticlib using `/WHOLEARCHIVE`.
141                // See #129020
142                true,
143                &[],
144            ) {
145                sess.dcx()
146                    .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() });
147            }
148        }
149    }
150
151    fn extract_bundled_libs<'a>(
152        &'a self,
153        rlib: &'a Path,
154        outdir: &Path,
155        bundled_lib_file_names: &FxIndexSet<Symbol>,
156    ) -> Result<(), ExtractBundledLibsError<'a>> {
157        let archive_map = unsafe {
158            Mmap::map(
159                File::open(rlib)
160                    .map_err(|e| ExtractBundledLibsError::OpenFile { rlib, error: Box::new(e) })?,
161            )
162            .map_err(|e| ExtractBundledLibsError::MmapFile { rlib, error: Box::new(e) })?
163        };
164        let archive = ArchiveFile::parse(&*archive_map)
165            .map_err(|e| ExtractBundledLibsError::ParseArchive { rlib, error: Box::new(e) })?;
166
167        for entry in archive.members() {
168            let entry = entry
169                .map_err(|e| ExtractBundledLibsError::ReadEntry { rlib, error: Box::new(e) })?;
170            let data = entry
171                .data(&*archive_map)
172                .map_err(|e| ExtractBundledLibsError::ArchiveMember { rlib, error: Box::new(e) })?;
173            let name = std::str::from_utf8(entry.name())
174                .map_err(|e| ExtractBundledLibsError::ConvertName { rlib, error: Box::new(e) })?;
175            if !bundled_lib_file_names.contains(&Symbol::intern(name)) {
176                continue; // We need to extract only native libraries.
177            }
178            let data = search_for_section(rlib, data, ".bundled_lib").map_err(|e| {
179                ExtractBundledLibsError::ExtractSection { rlib, error: Box::<dyn Error>::from(e) }
180            })?;
181            std::fs::write(&outdir.join(&name), data)
182                .map_err(|e| ExtractBundledLibsError::WriteFile { rlib, error: Box::new(e) })?;
183        }
184        Ok(())
185    }
186}
187
188fn create_mingw_dll_import_lib(
189    sess: &Session,
190    lib_name: &str,
191    items: Vec<ImportLibraryItem>,
192    output_path: &Path,
193) {
194    let def_file_path = output_path.with_extension("def");
195
196    let def_file_content = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("EXPORTS\n{0}",
                items.into_iter().map(|ImportLibraryItem { name, ordinal, ..
                                    }|
                                {
                                    match ordinal {
                                        Some(n) =>
                                            ::alloc::__export::must_use({
                                                    ::alloc::fmt::format(format_args!("{0} @{1} NONAME", name,
                                                            n))
                                                }),
                                        None => name,
                                    }
                                }).collect::<Vec<String>>().join("\n")))
    })format!(
197        "EXPORTS\n{}",
198        items
199            .into_iter()
200            .map(|ImportLibraryItem { name, ordinal, .. }| {
201                match ordinal {
202                    Some(n) => format!("{name} @{n} NONAME"),
203                    None => name,
204                }
205            })
206            .collect::<Vec<String>>()
207            .join("\n")
208    );
209
210    match std::fs::write(&def_file_path, def_file_content) {
211        Ok(_) => {}
212        Err(e) => {
213            sess.dcx().emit_fatal(ErrorWritingDEFFile { error: e });
214        }
215    };
216
217    // --no-leading-underscore: For the `import_name_type` feature to work, we need to be
218    // able to control the *exact* spelling of each of the symbols that are being imported:
219    // hence we don't want `dlltool` adding leading underscores automatically.
220    let dlltool = find_binutils_dlltool(sess);
221    // temp_prefix doesn't handle paths with spaces so
222    // use a relative path and set the current working directory
223    let cwd = output_path.parent().unwrap_or(output_path);
224    let temp_prefix = lib_name;
225    // dlltool target architecture args from:
226    // https://github.com/llvm/llvm-project-release-prs/blob/llvmorg-15.0.6/llvm/lib/ToolDrivers/llvm-dlltool/DlltoolDriver.cpp#L69
227    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 => { ::core::panicking::panic_fmt(format_args!("unsupported arch {0}", 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        .current_dir(cwd);
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        // dlltool returns '0' on failure, so check for error output instead.
259        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    if !(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc)
    {
    ::core::panicking::panic("assertion failed: sess.target.options.is_like_windows && !sess.target.options.is_like_msvc")
};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        // If we're compiling on Windows, always use "dlltool.exe".
283        "dlltool.exe"
284    } else {
285        // On other platforms, use the architecture-specific name.
286        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            // For non-standard architectures (e.g., aarch32) fallback to "dlltool".
292            _ => "dlltool",
293        }
294    }
295    .into();
296
297    // NOTE: it's not clear how useful it is to explicitly search PATH.
298    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    // The user didn't specify the location of the dlltool binary, and we weren't able
306    // to find the appropriate one on the PATH. Just return the name of the tool
307    // and let the invocation fail with a hopefully useful error message.
308    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: Option<Box<dyn FnMut(&str, Option<&RmetaLink>) -> bool + 'static>>,
318    ) -> io::Result<()>;
319
320    fn build(self: Box<Self>, output: &Path) -> bool;
321}
322
323fn target_archive_format_to_object_kind(format: &str) -> Option<ObjectArchiveKind> {
324    match format {
325        "gnu" => Some(ObjectArchiveKind::Gnu),
326        "bsd" => Some(ObjectArchiveKind::Bsd),
327        "darwin" => Some(ObjectArchiveKind::Bsd64),
328        "coff" => Some(ObjectArchiveKind::Coff),
329        "aix_big" => Some(ObjectArchiveKind::AixBig),
330        _ => None,
331    }
332}
333
334fn archive_kinds_compatible(actual: ObjectArchiveKind, expected: ObjectArchiveKind) -> bool {
335    if actual == expected {
336        return true;
337    }
338    #[allow(non_exhaustive_omitted_patterns)] match (actual, expected) {
    (ObjectArchiveKind::Unknown, _) |
        (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu) |
        (ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu64) |
        (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd) |
        (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64) |
        (ObjectArchiveKind::Gnu, ObjectArchiveKind::Coff) |
        (ObjectArchiveKind::Coff, ObjectArchiveKind::Gnu) |
        (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Coff) => true,
    _ => false,
}matches!(
339        (actual, expected),
340        // An archive without long filenames or symbol table is detected as Unknown;
341        // this is compatible with any target format.
342        (ObjectArchiveKind::Unknown, _)
343        // 64-bit symbol table variants are compatible with their 32-bit counterparts
344        | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu)
345        | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu64)
346        | (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd)
347        | (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64)
348        // GNU and COFF archives share the same magic and member header format;
349        // only the symbol table layout differs.
350        | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Coff)
351        | (ObjectArchiveKind::Coff, ObjectArchiveKind::Gnu)
352        | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Coff)
353    )
354}
355
356fn archive_kind_display_name(kind: ObjectArchiveKind) -> String {
357    match kind {
358        ObjectArchiveKind::Gnu | ObjectArchiveKind::Gnu64 => "GNU".to_string(),
359        ObjectArchiveKind::Bsd => "BSD".to_string(),
360        ObjectArchiveKind::Bsd64 => "Darwin".to_string(),
361        ObjectArchiveKind::Coff => "COFF".to_string(),
362        ObjectArchiveKind::AixBig => "AIX big".to_string(),
363        _ => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", kind))
    })format!("{kind:?}"),
364    }
365}
366
367pub struct ArArchiveBuilderBuilder;
368
369impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder {
370    fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a> {
371        Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER))
372    }
373}
374
375#[must_use = "must call build() to finish building the archive"]
376pub struct ArArchiveBuilder<'a> {
377    sess: &'a Session,
378    object_reader: &'static ObjectReader,
379
380    src_archives: Vec<(PathBuf, Mmap)>,
381    // Don't use an `HashMap` here, as the order is important. `lib.rmeta` needs
382    // to be at the end of an archive in some cases for linkers to not get confused.
383    entries: Vec<(Vec<u8>, ArchiveEntry)>,
384}
385
386#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ArchiveEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            ArchiveEntry::FromArchive {
                archive_index: __self_0, file_range: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "FromArchive", "archive_index", __self_0, "file_range",
                    &__self_1),
            ArchiveEntry::File(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "File",
                    &__self_0),
        }
    }
}Debug)]
387enum ArchiveEntry {
388    FromArchive { archive_index: usize, file_range: (u64, u64) },
389    File(PathBuf),
390}
391
392impl<'a> ArArchiveBuilder<'a> {
393    pub fn new(sess: &'a Session, object_reader: &'static ObjectReader) -> ArArchiveBuilder<'a> {
394        ArArchiveBuilder { sess, object_reader, src_archives: ::alloc::vec::Vec::new()vec![], entries: ::alloc::vec::Vec::new()vec![] }
395    }
396}
397
398fn try_filter_fat_archs(
399    archs: &[impl FatArch],
400    target_arch: object::Architecture,
401    archive_path: &Path,
402    archive_map_data: &[u8],
403) -> io::Result<Option<PathBuf>> {
404    let desired = match archs.iter().find(|a| a.architecture() == target_arch) {
405        Some(a) => a,
406        None => return Ok(None),
407    };
408
409    let (mut new_f, extracted_path) = tempfile::Builder::new()
410        .suffix(archive_path.file_name().unwrap())
411        .tempfile()?
412        .keep()
413        .unwrap();
414
415    new_f.write_all(
416        desired.data(archive_map_data).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?,
417    )?;
418
419    Ok(Some(extracted_path))
420}
421
422pub fn try_extract_macho_fat_archive(
423    sess: &Session,
424    archive_path: &Path,
425) -> io::Result<Option<PathBuf>> {
426    let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
427    let target_arch = match sess.target.arch {
428        Arch::AArch64 => object::Architecture::Aarch64,
429        Arch::X86_64 => object::Architecture::X86_64,
430        _ => return Ok(None),
431    };
432
433    if let Ok(h) = object::read::macho::MachOFatFile32::parse(&*archive_map) {
434        let archs = h.arches();
435        try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
436    } else if let Ok(h) = object::read::macho::MachOFatFile64::parse(&*archive_map) {
437        let archs = h.arches();
438        try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
439    } else {
440        // Not a FatHeader at all, just return None.
441        Ok(None)
442    }
443}
444
445impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
446    fn add_archive(
447        &mut self,
448        archive_path: &Path,
449        mut skip: Option<Box<dyn FnMut(&str, Option<&RmetaLink>) -> bool + 'static>>,
450    ) -> io::Result<()> {
451        let mut archive_path = archive_path.to_path_buf();
452        if self.sess.target.llvm_target.contains("-apple-macosx")
453            && let Some(new_archive_path) = try_extract_macho_fat_archive(self.sess, &archive_path)?
454        {
455            archive_path = new_archive_path
456        }
457
458        if self.src_archives.iter().any(|archive| archive.0 == archive_path) {
459            return Ok(());
460        }
461
462        let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
463        let archive = ArchiveFile::parse(&*archive_map)
464            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
465        let metadata_link =
466            skip.as_ref().and_then(|_| rmeta_link::read(&archive, &archive_map, &archive_path));
467        let archive_index = self.src_archives.len();
468
469        if let Some(expected_kind) =
470            target_archive_format_to_object_kind(&self.sess.target.archive_format)
471        {
472            let actual_kind = archive.kind();
473            if !archive_kinds_compatible(actual_kind, expected_kind) {
474                self.sess.dcx().emit_warn(crate::errors::IncompatibleArchiveFormat {
475                    path: archive_path.clone(),
476                    actual: archive_kind_display_name(actual_kind),
477                    expected: archive_kind_display_name(expected_kind),
478                });
479            }
480        }
481
482        for entry in archive.members() {
483            let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
484            let file_name = String::from_utf8(entry.name().to_vec())
485                .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
486            let drop = skip.as_mut().is_some_and(|f| f(&file_name, metadata_link.as_ref()));
487            if !drop {
488                if entry.is_thin() {
489                    let member_path = archive_path.parent().unwrap().join(Path::new(&file_name));
490                    self.entries.push((file_name.into_bytes(), ArchiveEntry::File(member_path)));
491                } else {
492                    self.entries.push((
493                        file_name.into_bytes(),
494                        ArchiveEntry::FromArchive { archive_index, file_range: entry.file_range() },
495                    ));
496                }
497            }
498        }
499
500        self.src_archives.push((archive_path, archive_map));
501        Ok(())
502    }
503
504    /// Adds an arbitrary file to this archive
505    fn add_file(&mut self, file: &Path) {
506        self.entries.push((
507            file.file_name().unwrap().to_str().unwrap().to_string().into_bytes(),
508            ArchiveEntry::File(file.to_owned()),
509        ));
510    }
511
512    /// Combine the provided files, rlibs, and native libraries into a single
513    /// `Archive`.
514    fn build(self: Box<Self>, output: &Path) -> bool {
515        let sess = self.sess;
516        match self.build_inner(output) {
517            Ok(any_members) => any_members,
518            Err(error) => {
519                sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
520            }
521        }
522    }
523}
524
525impl<'a> ArArchiveBuilder<'a> {
526    fn build_inner(self, output: &Path) -> io::Result<bool> {
527        let archive_kind = match &*self.sess.target.archive_format {
528            "gnu" => ArchiveKind::Gnu,
529            "bsd" => ArchiveKind::Bsd,
530            "darwin" => ArchiveKind::Darwin,
531            "coff" => ArchiveKind::Coff,
532            "aix_big" => ArchiveKind::AixBig,
533            kind => {
534                self.sess.dcx().emit_fatal(UnknownArchiveKind { kind });
535            }
536        };
537
538        let mut entries = Vec::new();
539
540        for (entry_name, entry) in self.entries {
541            let data =
542                match entry {
543                    ArchiveEntry::FromArchive { archive_index, file_range } => {
544                        let src_archive = &self.src_archives[archive_index];
545                        let archive_data = &src_archive.1;
546                        let start = file_range.0 as usize;
547                        let end = start + file_range.1 as usize;
548                        let Some(data) = archive_data.get(start..end) else {
549                            return Err(io_error_context(
550                                "invalid archive member",
551                                io::Error::new(
552                                    io::ErrorKind::InvalidData,
553                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("archive member at offset {3} with size {0} exceeds archive size {1} in `{2}`",
                file_range.1, archive_data.len(), src_archive.0.display(),
                start))
    })format!(
554                                        "archive member at offset {start} with size {} \
555                                         exceeds archive size {} in `{}`",
556                                        file_range.1,
557                                        archive_data.len(),
558                                        src_archive.0.display(),
559                                    ),
560                                ),
561                            ));
562                        };
563
564                        Box::new(data) as Box<dyn AsRef<[u8]>>
565                    }
566                    ArchiveEntry::File(file) => unsafe {
567                        Box::new(
568                            Mmap::map(File::open(file).map_err(|err| {
569                                io_error_context("failed to open object file", err)
570                            })?)
571                            .map_err(|err| io_error_context("failed to map object file", err))?,
572                        ) as Box<dyn AsRef<[u8]>>
573                    },
574                };
575
576            entries.push(NewArchiveMember {
577                buf: data,
578                object_reader: self.object_reader,
579                member_name: String::from_utf8(entry_name).unwrap(),
580                mtime: 0,
581                uid: 0,
582                gid: 0,
583                perms: 0o644,
584            })
585        }
586
587        // Write to a temporary file first before atomically renaming to the final name.
588        // This prevents programs (including rustc) from attempting to read a partial archive.
589        // It also enables writing an archive with the same filename as a dependency on Windows as
590        // required by a test.
591        // The tempfile crate currently uses 0o600 as mode for the temporary files and directories
592        // it creates. We need it to be the default mode for back compat reasons however. (See
593        // #107495) To handle this we are telling tempfile to create a temporary directory instead
594        // and then inside this directory create a file using File::create.
595        let archive_tmpdir = TempDirBuilder::new()
596            .suffix(".temp-archive")
597            .tempdir_in(output.parent().unwrap_or_else(|| Path::new("")))
598            .map_err(|err| {
599                io_error_context("couldn't create a directory for the temp file", err)
600            })?;
601        let archive_tmpfile_path = archive_tmpdir.path().join("tmp.a");
602        let archive_tmpfile = File::create_new(&archive_tmpfile_path)
603            .map_err(|err| io_error_context("couldn't create the temp file", err))?;
604
605        let mut archive_tmpfile = BufWriter::new(archive_tmpfile);
606        write_archive_to_stream(
607            &mut archive_tmpfile,
608            &entries,
609            archive_kind,
610            false,
611            /* is_ec = */ Some(self.sess.target.arch == Arch::Arm64EC),
612        )?;
613        archive_tmpfile.flush()?;
614        drop(archive_tmpfile);
615
616        let any_entries = !entries.is_empty();
617        drop(entries);
618        // Drop src_archives to unmap all input archives, which is necessary if we want to write the
619        // output archive to the same location as an input archive on Windows.
620        drop(self.src_archives);
621
622        fs::rename(archive_tmpfile_path, output)
623            .map_err(|err| io_error_context("failed to rename archive file", err))?;
624        archive_tmpdir
625            .close()
626            .map_err(|err| io_error_context("failed to remove temporary directory", err))?;
627
628        Ok(any_entries)
629    }
630}
631
632fn io_error_context(context: &str, err: io::Error) -> io::Error {
633    io::Error::new(io::ErrorKind::Other, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}: {1}", context, err))
    })format!("{context}: {err}"))
634}