1 //! A helper class for dealing with static archives
4 use std
::ffi
::{CStr, CString, OsString}
;
7 use std
::path
::{Path, PathBuf}
;
11 use crate::llvm
::archive_ro
::{ArchiveRO, Child}
;
12 use crate::llvm
::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}
;
13 use rustc_codegen_ssa
::back
::archive
::{ArchiveBuilder, ArchiveBuilderBuilder}
;
14 use rustc_session
::cstore
::{DllCallingConvention, DllImport}
;
15 use rustc_session
::Session
;
17 /// Helper for adding many files to an archive.
18 #[must_use = "must call build() to finish building the archive"]
19 pub struct LlvmArchiveBuilder
<'a
> {
21 additions
: Vec
<Addition
>,
25 File { path: PathBuf, name_in_archive: String }
,
26 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> }
,
30 fn path(&self) -> &Path
{
32 Addition
::File { path, .. }
| Addition
::Archive { path, .. }
=> path
,
37 fn is_relevant_child(c
: &Child
<'_
>) -> bool
{
39 Some(name
) => !name
.contains("SYMDEF"),
44 /// Map machine type strings to values of LLVM's MachineTypes enum.
45 fn llvm_machine_type(cpu
: &str) -> LLVMMachineType
{
47 "x86_64" => LLVMMachineType
::AMD64
,
48 "x86" => LLVMMachineType
::I386
,
49 "aarch64" => LLVMMachineType
::ARM64
,
50 "arm" => LLVMMachineType
::ARM
,
51 _
=> panic
!("unsupported cpu type {}", cpu
),
55 impl<'a
> ArchiveBuilder
<'a
> for LlvmArchiveBuilder
<'a
> {
59 skip
: Box
<dyn FnMut(&str) -> bool
+ '
static>,
61 let archive_ro
= match ArchiveRO
::open(archive
) {
63 Err(e
) => return Err(io
::Error
::new(io
::ErrorKind
::Other
, e
)),
65 if self.additions
.iter().any(|ar
| ar
.path() == archive
) {
68 self.additions
.push(Addition
::Archive
{
69 path
: archive
.to_path_buf(),
76 /// Adds an arbitrary file to this archive
77 fn add_file(&mut self, file
: &Path
) {
78 let name
= file
.file_name().unwrap().to_str().unwrap();
80 .push(Addition
::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }
);
83 /// Combine the provided files, rlibs, and native libraries into a single
85 fn build(mut self: Box
<Self>, output
: &Path
) -> bool
{
86 match self.build_with_llvm(output
) {
87 Ok(any_members
) => any_members
,
88 Err(e
) => self.sess
.fatal(&format
!("failed to build archive: {}", e
)),
93 pub struct LlvmArchiveBuilderBuilder
;
95 impl ArchiveBuilderBuilder
for LlvmArchiveBuilderBuilder
{
96 fn new_archive_builder
<'a
>(&self, sess
: &'a Session
) -> Box
<dyn ArchiveBuilder
<'a
> + 'a
> {
97 Box
::new(LlvmArchiveBuilder { sess, additions: Vec::new() }
)
100 fn create_dll_import_lib(
104 dll_imports
: &[DllImport
],
108 let mut output_path
: PathBuf
= tmpdir
.to_path_buf();
109 output_path
.push(format
!("{}_imports", lib_name
));
110 output_path
.with_extension("lib")
113 let target
= &sess
.target
;
114 let mingw_gnu_toolchain
= target
.vendor
== "pc"
115 && target
.os
== "windows"
116 && target
.env
== "gnu"
117 && target
.abi
.is_empty();
119 let import_name_and_ordinal_vector
: Vec
<(String
, Option
<u16>)> = dll_imports
121 .map(|import
: &DllImport
| {
122 if sess
.target
.arch
== "x86" {
124 LlvmArchiveBuilder
::i686_decorated_name(import
, mingw_gnu_toolchain
),
128 (import
.name
.to_string(), import
.ordinal
)
133 if mingw_gnu_toolchain
{
134 // The binutils linker used on -windows-gnu targets cannot read the import
135 // libraries generated by LLVM: in our attempts, the linker produced an .EXE
136 // that loaded but crashed with an AV upon calling one of the imported
137 // functions. Therefore, use binutils to create the import library instead,
138 // by writing a .DEF file to the temp dir and calling binutils's dlltool.
139 let def_file_path
= tmpdir
.join(format
!("{}_imports", lib_name
)).with_extension("def");
141 let def_file_content
= format
!(
143 import_name_and_ordinal_vector
145 .map(|(name
, ordinal
)| {
147 Some(n
) => format
!("{} @{} NONAME", name
, n
),
151 .collect
::<Vec
<String
>>()
155 match std
::fs
::write(&def_file_path
, def_file_content
) {
158 sess
.fatal(&format
!("Error writing .DEF file: {}", e
));
162 let dlltool
= find_binutils_dlltool(sess
);
163 let result
= std
::process
::Command
::new(dlltool
)
166 def_file_path
.to_str().unwrap(),
170 output_path
.to_str().unwrap(),
176 sess
.fatal(&format
!("Error calling dlltool: {}", e
));
178 Ok(output
) if !output
.status
.success() => sess
.fatal(&format
!(
179 "Dlltool could not create import library: {}\n{}",
180 String
::from_utf8_lossy(&output
.stdout
),
181 String
::from_utf8_lossy(&output
.stderr
)
186 // we've checked for \0 characters in the library name already
187 let dll_name_z
= CString
::new(lib_name
).unwrap();
189 let output_path_z
= rustc_fs_util
::path_to_c_string(&output_path
);
191 tracing
::trace
!("invoking LLVMRustWriteImportLibrary");
192 tracing
::trace
!(" dll_name {:#?}", dll_name_z
);
193 tracing
::trace
!(" output_path {}", output_path
.display());
198 .map(|import
| import
.name
.to_string())
203 // All import names are Rust identifiers and therefore cannot contain \0 characters.
204 // FIXME: when support for #[link_name] is implemented, ensure that the import names
205 // still don't contain any \0 characters. Also need to check that the names don't
206 // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
207 // in definition files.
208 let cstring_import_name_and_ordinal_vector
: Vec
<(CString
, Option
<u16>)> =
209 import_name_and_ordinal_vector
211 .map(|(name
, ordinal
)| (CString
::new(name
).unwrap(), ordinal
))
214 let ffi_exports
: Vec
<LLVMRustCOFFShortExport
> = cstring_import_name_and_ordinal_vector
216 .map(|(name_z
, ordinal
)| LLVMRustCOFFShortExport
::new(name_z
.as_ptr(), *ordinal
))
218 let result
= unsafe {
219 crate::llvm
::LLVMRustWriteImportLibrary(
221 output_path_z
.as_ptr(),
222 ffi_exports
.as_ptr(),
224 llvm_machine_type(&sess
.target
.arch
) as u16,
225 !sess
.target
.is_like_msvc
,
229 if result
== crate::llvm
::LLVMRustResult
::Failure
{
231 "Error creating import library for {}: {}",
233 llvm
::last_error().unwrap_or("unknown LLVM error".to_string())
242 impl<'a
> LlvmArchiveBuilder
<'a
> {
243 fn build_with_llvm(&mut self, output
: &Path
) -> io
::Result
<bool
> {
244 let kind
= &*self.sess
.target
.archive_format
;
245 let kind
= kind
.parse
::<ArchiveKind
>().map_err(|_
| kind
).unwrap_or_else(|kind
| {
246 self.sess
.fatal(&format
!("Don't know how to build archive of type: {}", kind
))
249 let mut additions
= mem
::take(&mut self.additions
);
250 let mut strings
= Vec
::new();
251 let mut members
= Vec
::new();
253 let dst
= CString
::new(output
.to_str().unwrap())?
;
256 for addition
in &mut additions
{
258 Addition
::File { path, name_in_archive }
=> {
259 let path
= CString
::new(path
.to_str().unwrap())?
;
260 let name
= CString
::new(name_in_archive
.clone())?
;
261 members
.push(llvm
::LLVMRustArchiveMemberNew(
269 Addition
::Archive { archive, skip, .. }
=> {
270 for child
in archive
.iter() {
271 let child
= child
.map_err(string_to_io_error
)?
;
272 if !is_relevant_child(&child
) {
275 let child_name
= child
.name().unwrap();
276 if skip(child_name
) {
280 // It appears that LLVM's archive writer is a little
281 // buggy if the name we pass down isn't just the
282 // filename component, so chop that off here and
285 // See LLVM bug 25877 for more info.
287 Path
::new(child_name
).file_name().unwrap().to_str().unwrap();
288 let name
= CString
::new(child_name
)?
;
289 let m
= llvm
::LLVMRustArchiveMemberNew(
301 let r
= llvm
::LLVMRustWriteArchive(
303 members
.len() as libc
::size_t
,
304 members
.as_ptr() as *const &_
,
308 let ret
= if r
.into_result().is_err() {
309 let err
= llvm
::LLVMRustGetLastError();
310 let msg
= if err
.is_null() {
311 "failed to write archive".into()
313 String
::from_utf8_lossy(CStr
::from_ptr(err
).to_bytes())
315 Err(io
::Error
::new(io
::ErrorKind
::Other
, msg
))
317 Ok(!members
.is_empty())
319 for member
in members
{
320 llvm
::LLVMRustArchiveMemberFree(member
);
326 fn i686_decorated_name(import
: &DllImport
, mingw
: bool
) -> String
{
327 let name
= import
.name
;
328 let prefix
= if mingw { "" }
else { "_" }
;
330 match import
.calling_convention
{
331 DllCallingConvention
::C
=> format
!("{}{}", prefix
, name
),
332 DllCallingConvention
::Stdcall(arg_list_size
) => {
333 format
!("{}{}@{}", prefix
, name
, arg_list_size
)
335 DllCallingConvention
::Fastcall(arg_list_size
) => format
!("@{}@{}", name
, arg_list_size
),
336 DllCallingConvention
::Vectorcall(arg_list_size
) => {
337 format
!("{}@@{}", name
, arg_list_size
)
343 fn string_to_io_error(s
: String
) -> io
::Error
{
344 io
::Error
::new(io
::ErrorKind
::Other
, format
!("bad archive: {}", s
))
347 fn find_binutils_dlltool(sess
: &Session
) -> OsString
{
348 assert
!(sess
.target
.options
.is_like_windows
&& !sess
.target
.options
.is_like_msvc
);
349 if let Some(dlltool_path
) = &sess
.opts
.unstable_opts
.dlltool
{
350 return dlltool_path
.clone().into_os_string();
353 let mut tool_name
: OsString
= if sess
.host
.arch
!= sess
.target
.arch
{
354 // We are cross-compiling, so we need the tool with the prefix matching our target
355 if sess
.target
.arch
== "x86" {
356 "i686-w64-mingw32-dlltool"
358 "x86_64-w64-mingw32-dlltool"
361 // We are not cross-compiling, so we just want `dlltool`
366 if sess
.host
.options
.is_like_windows
{
367 // If we're compiling on Windows, add the .exe suffix
368 tool_name
.push(".exe");
371 // NOTE: it's not clear how useful it is to explicitly search PATH.
372 for dir
in env
::split_paths(&env
::var_os("PATH").unwrap_or_default()) {
373 let full_path
= dir
.join(&tool_name
);
374 if full_path
.is_file() {
375 return full_path
.into_os_string();
379 // The user didn't specify the location of the dlltool binary, and we weren't able
380 // to find the appropriate one on the PATH. Just return the name of the tool
381 // and let the invocation fail with a hopefully useful error message.