]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
1 | //! A helper class for dealing with static archives |
2 | ||
dfeec247 | 3 | use std::ffi::{CStr, CString}; |
c1a9b12d SL |
4 | use std::io; |
5 | use std::mem; | |
6 | use std::path::{Path, PathBuf}; | |
e9174d1e | 7 | use std::ptr; |
c1a9b12d SL |
8 | use std::str; |
9 | ||
9fa01778 | 10 | use crate::llvm::archive_ro::{ArchiveRO, Child}; |
17df50a5 | 11 | use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}; |
dfeec247 | 12 | use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder}; |
f9f354fc | 13 | use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME}; |
17df50a5 | 14 | use rustc_data_structures::temp_dir::MaybeTempDir; |
136023e0 | 15 | use rustc_middle::middle::cstore::{DllCallingConvention, DllImport}; |
ba9703b0 | 16 | use rustc_session::Session; |
dfeec247 | 17 | use rustc_span::symbol::Symbol; |
c1a9b12d | 18 | |
48663c56 | 19 | struct ArchiveConfig<'a> { |
c1a9b12d SL |
20 | pub sess: &'a Session, |
21 | pub dst: PathBuf, | |
22 | pub src: Option<PathBuf>, | |
23 | pub lib_search_paths: Vec<PathBuf>, | |
c1a9b12d SL |
24 | } |
25 | ||
abe05a73 | 26 | /// Helper for adding many files to an archive. |
c1a9b12d | 27 | #[must_use = "must call build() to finish building the archive"] |
48663c56 | 28 | pub struct LlvmArchiveBuilder<'a> { |
c1a9b12d | 29 | config: ArchiveConfig<'a>, |
c1a9b12d SL |
30 | removals: Vec<String>, |
31 | additions: Vec<Addition>, | |
32 | should_update_symbols: bool, | |
33 | src_archive: Option<Option<ArchiveRO>>, | |
34 | } | |
35 | ||
36 | enum Addition { | |
dfeec247 XL |
37 | File { path: PathBuf, name_in_archive: String }, |
38 | Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> }, | |
c1a9b12d SL |
39 | } |
40 | ||
416331ca XL |
41 | impl Addition { |
42 | fn path(&self) -> &Path { | |
43 | match self { | |
44 | Addition::File { path, .. } | Addition::Archive { path, .. } => path, | |
45 | } | |
46 | } | |
47 | } | |
48 | ||
9fa01778 | 49 | fn is_relevant_child(c: &Child<'_>) -> bool { |
c1a9b12d SL |
50 | match c.name() { |
51 | Some(name) => !name.contains("SYMDEF"), | |
52 | None => false, | |
53 | } | |
54 | } | |
55 | ||
dfeec247 | 56 | fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) -> ArchiveConfig<'a> { |
48663c56 XL |
57 | use rustc_codegen_ssa::back::link::archive_search_paths; |
58 | ArchiveConfig { | |
59 | sess, | |
60 | dst: output.to_path_buf(), | |
61 | src: input.map(|p| p.to_path_buf()), | |
62 | lib_search_paths: archive_search_paths(sess), | |
63 | } | |
64 | } | |
65 | ||
17df50a5 XL |
66 | /// Map machine type strings to values of LLVM's MachineTypes enum. |
67 | fn llvm_machine_type(cpu: &str) -> LLVMMachineType { | |
68 | match cpu { | |
69 | "x86_64" => LLVMMachineType::AMD64, | |
70 | "x86" => LLVMMachineType::I386, | |
71 | "aarch64" => LLVMMachineType::ARM64, | |
72 | "arm" => LLVMMachineType::ARM, | |
73 | _ => panic!("unsupported cpu type {}", cpu), | |
74 | } | |
75 | } | |
76 | ||
48663c56 | 77 | impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> { |
9fa01778 | 78 | /// Creates a new static archive, ready for modifying the archive specified |
c1a9b12d | 79 | /// by `config`. |
dfeec247 | 80 | fn new(sess: &'a Session, output: &Path, input: Option<&Path>) -> LlvmArchiveBuilder<'a> { |
48663c56 XL |
81 | let config = archive_config(sess, output, input); |
82 | LlvmArchiveBuilder { | |
3b2f2976 | 83 | config, |
c1a9b12d SL |
84 | removals: Vec::new(), |
85 | additions: Vec::new(), | |
86 | should_update_symbols: false, | |
87 | src_archive: None, | |
88 | } | |
89 | } | |
90 | ||
91 | /// Removes a file from this archive | |
48663c56 | 92 | fn remove_file(&mut self, file: &str) { |
c1a9b12d SL |
93 | self.removals.push(file.to_string()); |
94 | } | |
95 | ||
96 | /// Lists all files in an archive | |
48663c56 | 97 | fn src_files(&mut self) -> Vec<String> { |
c1a9b12d | 98 | if self.src_archive().is_none() { |
dfeec247 | 99 | return Vec::new(); |
c1a9b12d | 100 | } |
a1dfa0c6 | 101 | |
c1a9b12d | 102 | let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap(); |
a1dfa0c6 | 103 | |
dfeec247 XL |
104 | archive |
105 | .iter() | |
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()) | |
111 | .collect() | |
c1a9b12d SL |
112 | } |
113 | ||
c1a9b12d SL |
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`. | |
17df50a5 XL |
116 | fn add_native_library(&mut self, name: Symbol, verbatim: bool) { |
117 | let location = | |
118 | find_library(name, verbatim, &self.config.lib_search_paths, self.config.sess); | |
3157f602 | 119 | self.add_archive(&location, |_| false).unwrap_or_else(|e| { |
dfeec247 XL |
120 | self.config.sess.fatal(&format!( |
121 | "failed to add native library {}: {}", | |
122 | location.to_string_lossy(), | |
123 | e | |
124 | )); | |
b039eaaf | 125 | }); |
c1a9b12d SL |
126 | } |
127 | ||
128 | /// Adds all of the contents of the rlib at the specified path to this | |
129 | /// archive. | |
130 | /// | |
131 | /// This ignores adding the bytecode from the rlib, and if LTO is enabled | |
132 | /// then the object file also isn't added. | |
dfeec247 XL |
133 | fn add_rlib( |
134 | &mut self, | |
135 | rlib: &Path, | |
136 | name: &str, | |
137 | lto: bool, | |
138 | skip_objects: bool, | |
139 | ) -> io::Result<()> { | |
c1a9b12d SL |
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 | |
8faf50e0 | 143 | let obj_start = name.to_owned(); |
c1a9b12d | 144 | |
3157f602 | 145 | self.add_archive(rlib, move |fname: &str| { |
f9f354fc XL |
146 | // Ignore metadata files, no matter the name. |
147 | if fname == METADATA_FILENAME { | |
dfeec247 | 148 | return true; |
476ff2be SL |
149 | } |
150 | ||
151 | // Don't include Rust objects if LTO is enabled | |
e74abb32 | 152 | if lto && looks_like_rust_object_file(fname) { |
dfeec247 | 153 | return true; |
476ff2be SL |
154 | } |
155 | ||
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")) { | |
dfeec247 | 159 | return true; |
476ff2be SL |
160 | } |
161 | ||
162 | // ok, don't skip this | |
ba9703b0 | 163 | false |
c1a9b12d SL |
164 | }) |
165 | } | |
166 | ||
c1a9b12d | 167 | /// Adds an arbitrary file to this archive |
48663c56 | 168 | fn add_file(&mut self, file: &Path) { |
c1a9b12d | 169 | let name = file.file_name().unwrap().to_str().unwrap(); |
dfeec247 XL |
170 | self.additions |
171 | .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }); | |
c1a9b12d SL |
172 | } |
173 | ||
abe05a73 XL |
174 | /// Indicate that the next call to `build` should update all symbols in |
175 | /// the archive (equivalent to running 'ar s' over it). | |
48663c56 | 176 | fn update_symbols(&mut self) { |
c1a9b12d SL |
177 | self.should_update_symbols = true; |
178 | } | |
179 | ||
180 | /// Combine the provided files, rlibs, and native libraries into a single | |
181 | /// `Archive`. | |
48663c56 | 182 | fn build(mut self) { |
dfeec247 XL |
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)) | |
185 | }); | |
c1a9b12d | 186 | |
3157f602 XL |
187 | if let Err(e) = self.build_with_llvm(kind) { |
188 | self.config.sess.fatal(&format!("failed to build archive: {}", e)); | |
c1a9b12d | 189 | } |
c1a9b12d | 190 | } |
17df50a5 XL |
191 | |
192 | fn inject_dll_import_lib( | |
193 | &mut self, | |
194 | lib_name: &str, | |
195 | dll_imports: &[DllImport], | |
196 | tmpdir: &MaybeTempDir, | |
197 | ) { | |
198 | let output_path = { | |
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") | |
202 | }; | |
203 | ||
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 | |
210 | .iter() | |
136023e0 XL |
211 | .map(|import: &DllImport| { |
212 | if self.config.sess.target.arch == "x86" { | |
213 | LlvmArchiveBuilder::i686_decorated_name(import) | |
214 | } else { | |
215 | CString::new(import.name.to_string()).unwrap() | |
216 | } | |
17df50a5 XL |
217 | }) |
218 | .collect(); | |
219 | ||
220 | let output_path_z = rustc_fs_util::path_to_c_string(&output_path); | |
221 | ||
222 | tracing::trace!("invoking LLVMRustWriteImportLibrary"); | |
223 | tracing::trace!(" dll_name {:#?}", dll_name_z); | |
224 | tracing::trace!(" output_path {}", output_path.display()); | |
225 | tracing::trace!( | |
226 | " import names: {}", | |
227 | dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "), | |
228 | ); | |
229 | ||
230 | let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_vector | |
231 | .iter() | |
232 | .map(|name_z| LLVMRustCOFFShortExport::from_name(name_z.as_ptr())) | |
233 | .collect(); | |
234 | let result = unsafe { | |
235 | crate::llvm::LLVMRustWriteImportLibrary( | |
236 | dll_name_z.as_ptr(), | |
237 | output_path_z.as_ptr(), | |
238 | ffi_exports.as_ptr(), | |
239 | ffi_exports.len(), | |
240 | llvm_machine_type(&self.config.sess.target.arch) as u16, | |
241 | !self.config.sess.target.is_like_msvc, | |
242 | ) | |
243 | }; | |
244 | ||
245 | if result == crate::llvm::LLVMRustResult::Failure { | |
246 | self.config.sess.fatal(&format!( | |
247 | "Error creating import library for {}: {}", | |
248 | lib_name, | |
249 | llvm::last_error().unwrap_or("unknown LLVM error".to_string()) | |
250 | )); | |
251 | } | |
252 | ||
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(), | |
257 | e | |
258 | )); | |
259 | }); | |
260 | } | |
48663c56 XL |
261 | } |
262 | ||
263 | impl<'a> LlvmArchiveBuilder<'a> { | |
264 | fn src_archive(&mut self) -> Option<&ArchiveRO> { | |
265 | if let Some(ref a) = self.src_archive { | |
dfeec247 | 266 | return a.as_ref(); |
48663c56 XL |
267 | } |
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() | |
271 | } | |
272 | ||
dfeec247 XL |
273 | fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()> |
274 | where | |
275 | F: FnMut(&str) -> bool + 'static, | |
48663c56 | 276 | { |
416331ca | 277 | let archive_ro = match ArchiveRO::open(archive) { |
48663c56 XL |
278 | Ok(ar) => ar, |
279 | Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), | |
280 | }; | |
416331ca | 281 | if self.additions.iter().any(|ar| ar.path() == archive) { |
dfeec247 | 282 | return Ok(()); |
416331ca | 283 | } |
48663c56 | 284 | self.additions.push(Addition::Archive { |
416331ca XL |
285 | path: archive.to_path_buf(), |
286 | archive: archive_ro, | |
48663c56 XL |
287 | skip: Box::new(skip), |
288 | }); | |
289 | Ok(()) | |
290 | } | |
c1a9b12d | 291 | |
3157f602 | 292 | fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> { |
29967ef6 | 293 | let kind = &*self.config.sess.target.archive_format; |
3157f602 | 294 | kind.parse().map_err(|_| kind) |
c1a9b12d SL |
295 | } |
296 | ||
297 | fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> { | |
416331ca XL |
298 | let removals = mem::take(&mut self.removals); |
299 | let mut additions = mem::take(&mut self.additions); | |
c1a9b12d SL |
300 | let mut strings = Vec::new(); |
301 | let mut members = Vec::new(); | |
b7449926 XL |
302 | |
303 | let dst = CString::new(self.config.dst.to_str().unwrap())?; | |
304 | let should_update_symbols = self.should_update_symbols; | |
c1a9b12d SL |
305 | |
306 | unsafe { | |
307 | if let Some(archive) = self.src_archive() { | |
308 | for child in archive.iter() { | |
54a0048b | 309 | let child = child.map_err(string_to_io_error)?; |
c1a9b12d SL |
310 | let child_name = match child.name() { |
311 | Some(s) => s, | |
312 | None => continue, | |
313 | }; | |
314 | if removals.iter().any(|r| r == child_name) { | |
dfeec247 | 315 | continue; |
c1a9b12d SL |
316 | } |
317 | ||
54a0048b | 318 | let name = CString::new(child_name)?; |
dfeec247 XL |
319 | members.push(llvm::LLVMRustArchiveMemberNew( |
320 | ptr::null(), | |
321 | name.as_ptr(), | |
322 | Some(child.raw), | |
323 | )); | |
c1a9b12d SL |
324 | strings.push(name); |
325 | } | |
326 | } | |
b7449926 | 327 | for addition in &mut additions { |
c1a9b12d SL |
328 | match addition { |
329 | Addition::File { path, name_in_archive } => { | |
54a0048b | 330 | let path = CString::new(path.to_str().unwrap())?; |
b7449926 | 331 | let name = CString::new(name_in_archive.clone())?; |
dfeec247 XL |
332 | members.push(llvm::LLVMRustArchiveMemberNew( |
333 | path.as_ptr(), | |
334 | name.as_ptr(), | |
335 | None, | |
336 | )); | |
c1a9b12d SL |
337 | strings.push(path); |
338 | strings.push(name); | |
339 | } | |
416331ca | 340 | Addition::Archive { archive, skip, .. } => { |
7453a54e | 341 | for child in archive.iter() { |
54a0048b | 342 | let child = child.map_err(string_to_io_error)?; |
7453a54e | 343 | if !is_relevant_child(&child) { |
dfeec247 | 344 | continue; |
7453a54e | 345 | } |
c1a9b12d | 346 | let child_name = child.name().unwrap(); |
7453a54e | 347 | if skip(child_name) { |
dfeec247 | 348 | continue; |
7453a54e SL |
349 | } |
350 | ||
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 | |
354 | // pass it in. | |
355 | // | |
356 | // See LLVM bug 25877 for more info. | |
dfeec247 XL |
357 | let child_name = |
358 | Path::new(child_name).file_name().unwrap().to_str().unwrap(); | |
54a0048b | 359 | let name = CString::new(child_name)?; |
dfeec247 XL |
360 | let m = llvm::LLVMRustArchiveMemberNew( |
361 | ptr::null(), | |
362 | name.as_ptr(), | |
363 | Some(child.raw), | |
364 | ); | |
c1a9b12d SL |
365 | members.push(m); |
366 | strings.push(name); | |
367 | } | |
c1a9b12d SL |
368 | } |
369 | } | |
370 | } | |
371 | ||
dfeec247 XL |
372 | let r = llvm::LLVMRustWriteArchive( |
373 | dst.as_ptr(), | |
374 | members.len() as libc::size_t, | |
375 | members.as_ptr() as *const &_, | |
376 | should_update_symbols, | |
377 | kind, | |
378 | ); | |
5bcae85e | 379 | let ret = if r.into_result().is_err() { |
c1a9b12d SL |
380 | let err = llvm::LLVMRustGetLastError(); |
381 | let msg = if err.is_null() { | |
a1dfa0c6 | 382 | "failed to write archive".into() |
c1a9b12d SL |
383 | } else { |
384 | String::from_utf8_lossy(CStr::from_ptr(err).to_bytes()) | |
c1a9b12d SL |
385 | }; |
386 | Err(io::Error::new(io::ErrorKind::Other, msg)) | |
387 | } else { | |
388 | Ok(()) | |
389 | }; | |
390 | for member in members { | |
391 | llvm::LLVMRustArchiveMemberFree(member); | |
392 | } | |
a1dfa0c6 | 393 | ret |
c1a9b12d SL |
394 | } |
395 | } | |
136023e0 XL |
396 | |
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) | |
407 | } | |
408 | }) | |
409 | .unwrap() | |
410 | } | |
c1a9b12d | 411 | } |
7453a54e SL |
412 | |
413 | fn string_to_io_error(s: String) -> io::Error { | |
414 | io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) | |
415 | } |