1 //! A helper class for dealing with static archives
3 use std
::ffi
::{CStr, CString}
;
6 use std
::path
::{Path, PathBuf}
;
10 use crate::llvm
::archive_ro
::{ArchiveRO, Child}
;
11 use crate::llvm
::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}
;
12 use rustc_codegen_ssa
::back
::archive
::{find_library, ArchiveBuilder}
;
13 use rustc_codegen_ssa
::{looks_like_rust_object_file, METADATA_FILENAME}
;
14 use rustc_data_structures
::temp_dir
::MaybeTempDir
;
15 use rustc_middle
::middle
::cstore
::{DllCallingConvention, DllImport}
;
16 use rustc_session
::Session
;
17 use rustc_span
::symbol
::Symbol
;
19 struct ArchiveConfig
<'a
> {
20 pub sess
: &'a Session
,
22 pub src
: Option
<PathBuf
>,
23 pub lib_search_paths
: Vec
<PathBuf
>,
26 /// Helper for adding many files to an archive.
27 #[must_use = "must call build() to finish building the archive"]
28 pub struct LlvmArchiveBuilder
<'a
> {
29 config
: ArchiveConfig
<'a
>,
30 removals
: Vec
<String
>,
31 additions
: Vec
<Addition
>,
32 should_update_symbols
: bool
,
33 src_archive
: Option
<Option
<ArchiveRO
>>,
37 File { path: PathBuf, name_in_archive: String }
,
38 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> }
,
42 fn path(&self) -> &Path
{
44 Addition
::File { path, .. }
| Addition
::Archive { path, .. }
=> path
,
49 fn is_relevant_child(c
: &Child
<'_
>) -> bool
{
51 Some(name
) => !name
.contains("SYMDEF"),
56 fn archive_config
<'a
>(sess
: &'a Session
, output
: &Path
, input
: Option
<&Path
>) -> ArchiveConfig
<'a
> {
57 use rustc_codegen_ssa
::back
::link
::archive_search_paths
;
60 dst
: output
.to_path_buf(),
61 src
: input
.map(|p
| p
.to_path_buf()),
62 lib_search_paths
: archive_search_paths(sess
),
66 /// Map machine type strings to values of LLVM's MachineTypes enum.
67 fn llvm_machine_type(cpu
: &str) -> LLVMMachineType
{
69 "x86_64" => LLVMMachineType
::AMD64
,
70 "x86" => LLVMMachineType
::I386
,
71 "aarch64" => LLVMMachineType
::ARM64
,
72 "arm" => LLVMMachineType
::ARM
,
73 _
=> panic
!("unsupported cpu type {}", cpu
),
77 impl<'a
> ArchiveBuilder
<'a
> for LlvmArchiveBuilder
<'a
> {
78 /// Creates a new static archive, ready for modifying the archive specified
80 fn new(sess
: &'a Session
, output
: &Path
, input
: Option
<&Path
>) -> LlvmArchiveBuilder
<'a
> {
81 let config
= archive_config(sess
, output
, input
);
85 additions
: Vec
::new(),
86 should_update_symbols
: false,
91 /// Removes a file from this archive
92 fn remove_file(&mut self, file
: &str) {
93 self.removals
.push(file
.to_string());
96 /// Lists all files in an archive
97 fn src_files(&mut self) -> Vec
<String
> {
98 if self.src_archive().is_none() {
102 let archive
= self.src_archive
.as_ref().unwrap().as_ref().unwrap();
106 .filter_map(|child
| child
.ok())
107 .filter(is_relevant_child
)
108 .filter_map(|child
| child
.name())
109 .filter(|name
| !self.removals
.iter().any(|x
| x
== name
))
110 .map(|name
| name
.to_owned())
114 /// Adds all of the contents of a native library to this archive. This will
115 /// search in the relevant locations for a library named `name`.
116 fn add_native_library(&mut self, name
: Symbol
, verbatim
: bool
) {
118 find_library(name
, verbatim
, &self.config
.lib_search_paths
, self.config
.sess
);
119 self.add_archive(&location
, |_
| false).unwrap_or_else(|e
| {
120 self.config
.sess
.fatal(&format
!(
121 "failed to add native library {}: {}",
122 location
.to_string_lossy(),
128 /// Adds all of the contents of the rlib at the specified path to this
131 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
132 /// then the object file also isn't added.
139 ) -> io
::Result
<()> {
140 // Ignoring obj file starting with the crate name
141 // as simple comparison is not enough - there
142 // might be also an extra name suffix
143 let obj_start
= name
.to_owned();
145 self.add_archive(rlib
, move |fname
: &str| {
146 // Ignore metadata files, no matter the name.
147 if fname
== METADATA_FILENAME
{
151 // Don't include Rust objects if LTO is enabled
152 if lto
&& looks_like_rust_object_file(fname
) {
156 // Otherwise if this is *not* a rust object and we're skipping
157 // objects then skip this file
158 if skip_objects
&& (!fname
.starts_with(&obj_start
) || !fname
.ends_with(".o")) {
162 // ok, don't skip this
167 /// Adds an arbitrary file to this archive
168 fn add_file(&mut self, file
: &Path
) {
169 let name
= file
.file_name().unwrap().to_str().unwrap();
171 .push(Addition
::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }
);
174 /// Indicate that the next call to `build` should update all symbols in
175 /// the archive (equivalent to running 'ar s' over it).
176 fn update_symbols(&mut self) {
177 self.should_update_symbols
= true;
180 /// Combine the provided files, rlibs, and native libraries into a single
183 let kind
= self.llvm_archive_kind().unwrap_or_else(|kind
| {
184 self.config
.sess
.fatal(&format
!("Don't know how to build archive of type: {}", kind
))
187 if let Err(e
) = self.build_with_llvm(kind
) {
188 self.config
.sess
.fatal(&format
!("failed to build archive: {}", e
));
192 fn inject_dll_import_lib(
195 dll_imports
: &[DllImport
],
196 tmpdir
: &MaybeTempDir
,
199 let mut output_path
: PathBuf
= tmpdir
.as_ref().to_path_buf();
200 output_path
.push(format
!("{}_imports", lib_name
));
201 output_path
.with_extension("lib")
204 // we've checked for \0 characters in the library name already
205 let dll_name_z
= CString
::new(lib_name
).unwrap();
206 // All import names are Rust identifiers and therefore cannot contain \0 characters.
207 // FIXME: when support for #[link_name] implemented, ensure that import.name values don't
208 // have any \0 characters
209 let import_name_vector
: Vec
<CString
> = dll_imports
211 .map(|import
: &DllImport
| {
212 if self.config
.sess
.target
.arch
== "x86" {
213 LlvmArchiveBuilder
::i686_decorated_name(import
)
215 CString
::new(import
.name
.to_string()).unwrap()
220 let output_path_z
= rustc_fs_util
::path_to_c_string(&output_path
);
222 tracing
::trace
!("invoking LLVMRustWriteImportLibrary");
223 tracing
::trace
!(" dll_name {:#?}", dll_name_z
);
224 tracing
::trace
!(" output_path {}", output_path
.display());
227 dll_imports
.iter().map(|import
| import
.name
.to_string()).collect
::<Vec
<_
>>().join(", "),
230 let ffi_exports
: Vec
<LLVMRustCOFFShortExport
> = import_name_vector
232 .map(|name_z
| LLVMRustCOFFShortExport
::from_name(name_z
.as_ptr()))
234 let result
= unsafe {
235 crate::llvm
::LLVMRustWriteImportLibrary(
237 output_path_z
.as_ptr(),
238 ffi_exports
.as_ptr(),
240 llvm_machine_type(&self.config
.sess
.target
.arch
) as u16,
241 !self.config
.sess
.target
.is_like_msvc
,
245 if result
== crate::llvm
::LLVMRustResult
::Failure
{
246 self.config
.sess
.fatal(&format
!(
247 "Error creating import library for {}: {}",
249 llvm
::last_error().unwrap_or("unknown LLVM error".to_string())
253 self.add_archive(&output_path
, |_
| false).unwrap_or_else(|e
| {
254 self.config
.sess
.fatal(&format
!(
255 "failed to add native library {}: {}",
256 output_path
.display(),
263 impl<'a
> LlvmArchiveBuilder
<'a
> {
264 fn src_archive(&mut self) -> Option
<&ArchiveRO
> {
265 if let Some(ref a
) = self.src_archive
{
268 let src
= self.config
.src
.as_ref()?
;
269 self.src_archive
= Some(ArchiveRO
::open(src
).ok());
270 self.src_archive
.as_ref().unwrap().as_ref()
273 fn add_archive
<F
>(&mut self, archive
: &Path
, skip
: F
) -> io
::Result
<()>
275 F
: FnMut(&str) -> bool
+ '
static,
277 let archive_ro
= match ArchiveRO
::open(archive
) {
279 Err(e
) => return Err(io
::Error
::new(io
::ErrorKind
::Other
, e
)),
281 if self.additions
.iter().any(|ar
| ar
.path() == archive
) {
284 self.additions
.push(Addition
::Archive
{
285 path
: archive
.to_path_buf(),
287 skip
: Box
::new(skip
),
292 fn llvm_archive_kind(&self) -> Result
<ArchiveKind
, &str> {
293 let kind
= &*self.config
.sess
.target
.archive_format
;
294 kind
.parse().map_err(|_
| kind
)
297 fn build_with_llvm(&mut self, kind
: ArchiveKind
) -> io
::Result
<()> {
298 let removals
= mem
::take(&mut self.removals
);
299 let mut additions
= mem
::take(&mut self.additions
);
300 let mut strings
= Vec
::new();
301 let mut members
= Vec
::new();
303 let dst
= CString
::new(self.config
.dst
.to_str().unwrap())?
;
304 let should_update_symbols
= self.should_update_symbols
;
307 if let Some(archive
) = self.src_archive() {
308 for child
in archive
.iter() {
309 let child
= child
.map_err(string_to_io_error
)?
;
310 let child_name
= match child
.name() {
314 if removals
.iter().any(|r
| r
== child_name
) {
318 let name
= CString
::new(child_name
)?
;
319 members
.push(llvm
::LLVMRustArchiveMemberNew(
327 for addition
in &mut additions
{
329 Addition
::File { path, name_in_archive }
=> {
330 let path
= CString
::new(path
.to_str().unwrap())?
;
331 let name
= CString
::new(name_in_archive
.clone())?
;
332 members
.push(llvm
::LLVMRustArchiveMemberNew(
340 Addition
::Archive { archive, skip, .. }
=> {
341 for child
in archive
.iter() {
342 let child
= child
.map_err(string_to_io_error
)?
;
343 if !is_relevant_child(&child
) {
346 let child_name
= child
.name().unwrap();
347 if skip(child_name
) {
351 // It appears that LLVM's archive writer is a little
352 // buggy if the name we pass down isn't just the
353 // filename component, so chop that off here and
356 // See LLVM bug 25877 for more info.
358 Path
::new(child_name
).file_name().unwrap().to_str().unwrap();
359 let name
= CString
::new(child_name
)?
;
360 let m
= llvm
::LLVMRustArchiveMemberNew(
372 let r
= llvm
::LLVMRustWriteArchive(
374 members
.len() as libc
::size_t
,
375 members
.as_ptr() as *const &_
,
376 should_update_symbols
,
379 let ret
= if r
.into_result().is_err() {
380 let err
= llvm
::LLVMRustGetLastError();
381 let msg
= if err
.is_null() {
382 "failed to write archive".into()
384 String
::from_utf8_lossy(CStr
::from_ptr(err
).to_bytes())
386 Err(io
::Error
::new(io
::ErrorKind
::Other
, msg
))
390 for member
in members
{
391 llvm
::LLVMRustArchiveMemberFree(member
);
397 fn i686_decorated_name(import
: &DllImport
) -> CString
{
398 let name
= import
.name
;
399 // We verified during construction that `name` does not contain any NULL characters, so the
400 // conversion to CString is guaranteed to succeed.
401 CString
::new(match import
.calling_convention
{
402 DllCallingConvention
::C
=> format
!("_{}", name
),
403 DllCallingConvention
::Stdcall(arg_list_size
) => format
!("_{}@{}", name
, arg_list_size
),
404 DllCallingConvention
::Fastcall(arg_list_size
) => format
!("@{}@{}", name
, arg_list_size
),
405 DllCallingConvention
::Vectorcall(arg_list_size
) => {
406 format
!("{}@@{}", name
, arg_list_size
)
413 fn string_to_io_error(s
: String
) -> io
::Error
{
414 io
::Error
::new(io
::ErrorKind
::Other
, format
!("bad archive: {}", s
))