]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! A helper class for dealing with static archives | |
12 | ||
13 | use std::env; | |
14 | use std::ffi::{CString, CStr, OsString}; | |
15 | use std::fs::{self, File}; | |
16 | use std::io::prelude::*; | |
17 | use std::io; | |
18 | use std::mem; | |
19 | use std::path::{Path, PathBuf}; | |
20 | use std::process::{Command, Output, Stdio}; | |
e9174d1e | 21 | use std::ptr; |
c1a9b12d SL |
22 | use std::str; |
23 | ||
92a42be0 SL |
24 | use middle::cstore::CrateStore; |
25 | ||
c1a9b12d SL |
26 | use libc; |
27 | use llvm::archive_ro::{ArchiveRO, Child}; | |
28 | use llvm::{self, ArchiveKind}; | |
c1a9b12d SL |
29 | use rustc::session::Session; |
30 | use rustc_back::tempdir::TempDir; | |
31 | ||
32 | pub struct ArchiveConfig<'a> { | |
33 | pub sess: &'a Session, | |
34 | pub dst: PathBuf, | |
35 | pub src: Option<PathBuf>, | |
36 | pub lib_search_paths: Vec<PathBuf>, | |
37 | pub ar_prog: String, | |
38 | pub command_path: OsString, | |
39 | } | |
40 | ||
41 | /// Helper for adding many files to an archive with a single invocation of | |
42 | /// `ar`. | |
43 | #[must_use = "must call build() to finish building the archive"] | |
44 | pub struct ArchiveBuilder<'a> { | |
45 | config: ArchiveConfig<'a>, | |
46 | work_dir: TempDir, | |
47 | removals: Vec<String>, | |
48 | additions: Vec<Addition>, | |
49 | should_update_symbols: bool, | |
50 | src_archive: Option<Option<ArchiveRO>>, | |
51 | } | |
52 | ||
53 | enum Addition { | |
54 | File { | |
55 | path: PathBuf, | |
56 | name_in_archive: String, | |
57 | }, | |
58 | Archive { | |
59 | archive: ArchiveRO, | |
60 | archive_name: String, | |
61 | skip: Box<FnMut(&str) -> bool>, | |
62 | }, | |
63 | } | |
64 | ||
65 | enum Action<'a> { | |
66 | Remove(&'a [String]), | |
67 | AddObjects(&'a [&'a PathBuf], bool), | |
68 | UpdateSymbols, | |
69 | } | |
70 | ||
71 | pub fn find_library(name: &str, search_paths: &[PathBuf], sess: &Session) | |
72 | -> PathBuf { | |
73 | // On Windows, static libraries sometimes show up as libfoo.a and other | |
74 | // times show up as foo.lib | |
75 | let oslibname = format!("{}{}{}", | |
76 | sess.target.target.options.staticlib_prefix, | |
77 | name, | |
78 | sess.target.target.options.staticlib_suffix); | |
79 | let unixlibname = format!("lib{}.a", name); | |
80 | ||
81 | for path in search_paths { | |
82 | debug!("looking for {} inside {:?}", name, path); | |
83 | let test = path.join(&oslibname[..]); | |
84 | if test.exists() { return test } | |
85 | if oslibname != unixlibname { | |
86 | let test = path.join(&unixlibname[..]); | |
87 | if test.exists() { return test } | |
88 | } | |
89 | } | |
90 | sess.fatal(&format!("could not find native static library `{}`, \ | |
91 | perhaps an -L flag is missing?", name)); | |
92 | } | |
93 | ||
94 | fn is_relevant_child(c: &Child) -> bool { | |
95 | match c.name() { | |
96 | Some(name) => !name.contains("SYMDEF"), | |
97 | None => false, | |
98 | } | |
99 | } | |
100 | ||
101 | impl<'a> ArchiveBuilder<'a> { | |
102 | /// Create a new static archive, ready for modifying the archive specified | |
103 | /// by `config`. | |
104 | pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> { | |
105 | ArchiveBuilder { | |
106 | config: config, | |
107 | work_dir: TempDir::new("rsar").unwrap(), | |
108 | removals: Vec::new(), | |
109 | additions: Vec::new(), | |
110 | should_update_symbols: false, | |
111 | src_archive: None, | |
112 | } | |
113 | } | |
114 | ||
115 | /// Removes a file from this archive | |
116 | pub fn remove_file(&mut self, file: &str) { | |
117 | self.removals.push(file.to_string()); | |
118 | } | |
119 | ||
120 | /// Lists all files in an archive | |
121 | pub fn src_files(&mut self) -> Vec<String> { | |
122 | if self.src_archive().is_none() { | |
123 | return Vec::new() | |
124 | } | |
125 | let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap(); | |
126 | let ret = archive.iter() | |
7453a54e | 127 | .filter_map(|child| child.ok()) |
c1a9b12d SL |
128 | .filter(is_relevant_child) |
129 | .filter_map(|child| child.name()) | |
130 | .filter(|name| !self.removals.iter().any(|x| x == name)) | |
131 | .map(|name| name.to_string()) | |
132 | .collect(); | |
133 | return ret; | |
134 | } | |
135 | ||
136 | fn src_archive(&mut self) -> Option<&ArchiveRO> { | |
137 | if let Some(ref a) = self.src_archive { | |
138 | return a.as_ref() | |
139 | } | |
140 | let src = match self.config.src { | |
141 | Some(ref src) => src, | |
142 | None => return None, | |
143 | }; | |
144 | self.src_archive = Some(ArchiveRO::open(src)); | |
145 | self.src_archive.as_ref().unwrap().as_ref() | |
146 | } | |
147 | ||
148 | /// Adds all of the contents of a native library to this archive. This will | |
149 | /// search in the relevant locations for a library named `name`. | |
b039eaaf | 150 | pub fn add_native_library(&mut self, name: &str) { |
c1a9b12d SL |
151 | let location = find_library(name, &self.config.lib_search_paths, |
152 | self.config.sess); | |
b039eaaf SL |
153 | self.add_archive(&location, name, |_| false).unwrap_or_else(|e| { |
154 | self.config.sess.fatal(&format!("failed to add native library {}: {}", | |
155 | location.to_string_lossy(), e)); | |
156 | }); | |
c1a9b12d SL |
157 | } |
158 | ||
159 | /// Adds all of the contents of the rlib at the specified path to this | |
160 | /// archive. | |
161 | /// | |
162 | /// This ignores adding the bytecode from the rlib, and if LTO is enabled | |
163 | /// then the object file also isn't added. | |
164 | pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool) | |
165 | -> io::Result<()> { | |
166 | // Ignoring obj file starting with the crate name | |
167 | // as simple comparison is not enough - there | |
168 | // might be also an extra name suffix | |
169 | let obj_start = format!("{}", name); | |
170 | ||
171 | // Ignoring all bytecode files, no matter of | |
172 | // name | |
173 | let bc_ext = ".bytecode.deflate"; | |
92a42be0 SL |
174 | let metadata_filename = |
175 | self.config.sess.cstore.metadata_filename().to_owned(); | |
c1a9b12d SL |
176 | |
177 | self.add_archive(rlib, &name[..], move |fname: &str| { | |
178 | let skip_obj = lto && fname.starts_with(&obj_start) | |
179 | && fname.ends_with(".o"); | |
92a42be0 | 180 | skip_obj || fname.ends_with(bc_ext) || fname == metadata_filename |
c1a9b12d SL |
181 | }) |
182 | } | |
183 | ||
184 | fn add_archive<F>(&mut self, archive: &Path, name: &str, skip: F) | |
185 | -> io::Result<()> | |
186 | where F: FnMut(&str) -> bool + 'static | |
187 | { | |
188 | let archive = match ArchiveRO::open(archive) { | |
189 | Some(ar) => ar, | |
190 | None => return Err(io::Error::new(io::ErrorKind::Other, | |
191 | "failed to open archive")), | |
192 | }; | |
193 | self.additions.push(Addition::Archive { | |
194 | archive: archive, | |
195 | archive_name: name.to_string(), | |
196 | skip: Box::new(skip), | |
197 | }); | |
198 | Ok(()) | |
199 | } | |
200 | ||
201 | /// Adds an arbitrary file to this archive | |
202 | pub fn add_file(&mut self, file: &Path) { | |
203 | let name = file.file_name().unwrap().to_str().unwrap(); | |
204 | self.additions.push(Addition::File { | |
205 | path: file.to_path_buf(), | |
206 | name_in_archive: name.to_string(), | |
207 | }); | |
208 | } | |
209 | ||
210 | /// Indicate that the next call to `build` should updates all symbols in | |
211 | /// the archive (run 'ar s' over it). | |
212 | pub fn update_symbols(&mut self) { | |
213 | self.should_update_symbols = true; | |
214 | } | |
215 | ||
216 | /// Combine the provided files, rlibs, and native libraries into a single | |
217 | /// `Archive`. | |
218 | pub fn build(&mut self) { | |
219 | let res = match self.llvm_archive_kind() { | |
220 | Some(kind) => self.build_with_llvm(kind), | |
221 | None => self.build_with_ar_cmd(), | |
222 | }; | |
223 | if let Err(e) = res { | |
224 | self.config.sess.fatal(&format!("failed to build archive: {}", e)); | |
225 | } | |
226 | } | |
227 | ||
228 | pub fn llvm_archive_kind(&self) -> Option<ArchiveKind> { | |
229 | if unsafe { llvm::LLVMVersionMinor() < 7 } { | |
230 | return None | |
231 | } | |
232 | ||
233 | // Currently LLVM only supports writing archives in the 'gnu' format. | |
234 | match &self.config.sess.target.target.options.archive_format[..] { | |
235 | "gnu" => Some(ArchiveKind::K_GNU), | |
236 | "mips64" => Some(ArchiveKind::K_MIPS64), | |
237 | "bsd" => Some(ArchiveKind::K_BSD), | |
238 | "coff" => Some(ArchiveKind::K_COFF), | |
239 | _ => None, | |
240 | } | |
241 | } | |
242 | ||
243 | pub fn using_llvm(&self) -> bool { | |
244 | self.llvm_archive_kind().is_some() | |
245 | } | |
246 | ||
247 | fn build_with_ar_cmd(&mut self) -> io::Result<()> { | |
248 | let removals = mem::replace(&mut self.removals, Vec::new()); | |
249 | let additions = mem::replace(&mut self.additions, Vec::new()); | |
250 | let should_update_symbols = mem::replace(&mut self.should_update_symbols, | |
251 | false); | |
252 | ||
253 | // Don't use fs::copy because libs may be installed as read-only and we | |
254 | // want to modify this archive, so we use `io::copy` to not preserve | |
255 | // permission bits. | |
256 | if let Some(ref s) = self.config.src { | |
54a0048b SL |
257 | io::copy(&mut File::open(s)?, |
258 | &mut File::create(&self.config.dst)?)?; | |
c1a9b12d SL |
259 | } |
260 | ||
261 | if removals.len() > 0 { | |
262 | self.run(None, Action::Remove(&removals)); | |
263 | } | |
264 | ||
265 | let mut members = Vec::new(); | |
266 | for addition in additions { | |
267 | match addition { | |
268 | Addition::File { path, name_in_archive } => { | |
269 | let dst = self.work_dir.path().join(&name_in_archive); | |
54a0048b | 270 | fs::copy(&path, &dst)?; |
c1a9b12d SL |
271 | members.push(PathBuf::from(name_in_archive)); |
272 | } | |
273 | Addition::Archive { archive, archive_name, mut skip } => { | |
54a0048b SL |
274 | self.add_archive_members(&mut members, archive, |
275 | &archive_name, &mut *skip)?; | |
c1a9b12d SL |
276 | } |
277 | } | |
278 | } | |
279 | ||
280 | // Get an absolute path to the destination, so `ar` will work even | |
281 | // though we run it from `self.work_dir`. | |
282 | let mut objects = Vec::new(); | |
283 | let mut total_len = self.config.dst.to_string_lossy().len(); | |
284 | ||
285 | if members.is_empty() { | |
286 | if should_update_symbols { | |
287 | self.run(Some(self.work_dir.path()), Action::UpdateSymbols); | |
288 | } | |
289 | return Ok(()) | |
290 | } | |
291 | ||
292 | // Don't allow the total size of `args` to grow beyond 32,000 bytes. | |
293 | // Windows will raise an error if the argument string is longer than | |
294 | // 32,768, and we leave a bit of extra space for the program name. | |
295 | const ARG_LENGTH_LIMIT: usize = 32_000; | |
296 | ||
297 | for member_name in &members { | |
298 | let len = member_name.to_string_lossy().len(); | |
299 | ||
300 | // `len + 1` to account for the space that's inserted before each | |
301 | // argument. (Windows passes command-line arguments as a single | |
302 | // string, not an array of strings.) | |
303 | if total_len + len + 1 > ARG_LENGTH_LIMIT { | |
304 | // Add the archive members seen so far, without updating the | |
305 | // symbol table. | |
306 | self.run(Some(self.work_dir.path()), | |
307 | Action::AddObjects(&objects, false)); | |
308 | ||
309 | objects.clear(); | |
310 | total_len = self.config.dst.to_string_lossy().len(); | |
311 | } | |
312 | ||
313 | objects.push(member_name); | |
314 | total_len += len + 1; | |
315 | } | |
316 | ||
317 | // Add the remaining archive members, and update the symbol table if | |
318 | // necessary. | |
319 | self.run(Some(self.work_dir.path()), | |
320 | Action::AddObjects(&objects, should_update_symbols)); | |
321 | Ok(()) | |
322 | } | |
323 | ||
324 | fn add_archive_members(&mut self, members: &mut Vec<PathBuf>, | |
325 | archive: ArchiveRO, name: &str, | |
326 | skip: &mut FnMut(&str) -> bool) -> io::Result<()> { | |
327 | // Next, we must rename all of the inputs to "guaranteed unique names". | |
328 | // We write each file into `self.work_dir` under its new unique name. | |
329 | // The reason for this renaming is that archives are keyed off the name | |
330 | // of the files, so if two files have the same name they will override | |
331 | // one another in the archive (bad). | |
332 | // | |
333 | // We skip any files explicitly desired for skipping, and we also skip | |
334 | // all SYMDEF files as these are just magical placeholders which get | |
335 | // re-created when we make a new archive anyway. | |
7453a54e | 336 | for file in archive.iter() { |
54a0048b | 337 | let file = file.map_err(string_to_io_error)?; |
7453a54e SL |
338 | if !is_relevant_child(&file) { |
339 | continue | |
340 | } | |
c1a9b12d | 341 | let filename = file.name().unwrap(); |
7453a54e SL |
342 | if skip(filename) { |
343 | continue | |
344 | } | |
c1a9b12d SL |
345 | let filename = Path::new(filename).file_name().unwrap() |
346 | .to_str().unwrap(); | |
347 | ||
348 | // Archives on unix systems typically do not have slashes in | |
349 | // filenames as the `ar` utility generally only uses the last | |
350 | // component of a path for the filename list in the archive. On | |
351 | // Windows, however, archives assembled with `lib.exe` will preserve | |
352 | // the full path to the file that was placed in the archive, | |
353 | // including path separators. | |
354 | // | |
355 | // The code below is munging paths so it'll go wrong pretty quickly | |
356 | // if there's some unexpected slashes in the filename, so here we | |
357 | // just chop off everything but the filename component. Note that | |
358 | // this can cause duplicate filenames, but that's also handled below | |
359 | // as well. | |
360 | let filename = Path::new(filename).file_name().unwrap() | |
361 | .to_str().unwrap(); | |
362 | ||
363 | // An archive can contain files of the same name multiple times, so | |
364 | // we need to be sure to not have them overwrite one another when we | |
365 | // extract them. Consequently we need to find a truly unique file | |
366 | // name for us! | |
367 | let mut new_filename = String::new(); | |
368 | for n in 0.. { | |
369 | let n = if n == 0 {String::new()} else {format!("-{}", n)}; | |
370 | new_filename = format!("r{}-{}-{}", n, name, filename); | |
371 | ||
372 | // LLDB (as mentioned in back::link) crashes on filenames of | |
373 | // exactly | |
374 | // 16 bytes in length. If we're including an object file with | |
375 | // exactly 16-bytes of characters, give it some prefix so | |
376 | // that it's not 16 bytes. | |
377 | new_filename = if new_filename.len() == 16 { | |
378 | format!("lldb-fix-{}", new_filename) | |
379 | } else { | |
380 | new_filename | |
381 | }; | |
382 | ||
383 | let present = members.iter().filter_map(|p| { | |
384 | p.file_name().and_then(|f| f.to_str()) | |
385 | }).any(|s| s == new_filename); | |
386 | if !present { | |
387 | break | |
388 | } | |
389 | } | |
390 | let dst = self.work_dir.path().join(&new_filename); | |
54a0048b | 391 | File::create(&dst)?.write_all(file.data())?; |
c1a9b12d SL |
392 | members.push(PathBuf::from(new_filename)); |
393 | } | |
394 | Ok(()) | |
395 | } | |
396 | ||
397 | fn run(&self, cwd: Option<&Path>, action: Action) -> Output { | |
398 | let abs_dst = env::current_dir().unwrap().join(&self.config.dst); | |
399 | let ar = &self.config.ar_prog; | |
400 | let mut cmd = Command::new(ar); | |
401 | cmd.env("PATH", &self.config.command_path); | |
402 | cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); | |
403 | self.prepare_ar_action(&mut cmd, &abs_dst, action); | |
404 | info!("{:?}", cmd); | |
405 | ||
406 | if let Some(p) = cwd { | |
407 | cmd.current_dir(p); | |
408 | info!("inside {:?}", p.display()); | |
409 | } | |
410 | ||
411 | let sess = &self.config.sess; | |
412 | match cmd.spawn() { | |
413 | Ok(prog) => { | |
414 | let o = prog.wait_with_output().unwrap(); | |
415 | if !o.status.success() { | |
9cc50fc6 SL |
416 | sess.struct_err(&format!("{:?} failed with: {}", cmd, o.status)) |
417 | .note(&format!("stdout ---\n{}", | |
418 | str::from_utf8(&o.stdout).unwrap())) | |
419 | .note(&format!("stderr ---\n{}", | |
420 | str::from_utf8(&o.stderr).unwrap())) | |
421 | .emit(); | |
c1a9b12d SL |
422 | sess.abort_if_errors(); |
423 | } | |
424 | o | |
425 | }, | |
426 | Err(e) => { | |
427 | sess.fatal(&format!("could not exec `{}`: {}", | |
428 | self.config.ar_prog, e)); | |
429 | } | |
430 | } | |
431 | } | |
432 | ||
433 | fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) { | |
434 | match action { | |
435 | Action::Remove(files) => { | |
436 | cmd.arg("d").arg(dst).args(files); | |
437 | } | |
438 | Action::AddObjects(objs, update_symbols) => { | |
439 | cmd.arg(if update_symbols {"crs"} else {"crS"}) | |
440 | .arg(dst) | |
441 | .args(objs); | |
442 | } | |
443 | Action::UpdateSymbols => { | |
444 | cmd.arg("s").arg(dst); | |
445 | } | |
446 | } | |
447 | } | |
448 | ||
449 | fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> { | |
450 | let mut archives = Vec::new(); | |
451 | let mut strings = Vec::new(); | |
452 | let mut members = Vec::new(); | |
453 | let removals = mem::replace(&mut self.removals, Vec::new()); | |
454 | ||
455 | unsafe { | |
456 | if let Some(archive) = self.src_archive() { | |
457 | for child in archive.iter() { | |
54a0048b | 458 | let child = child.map_err(string_to_io_error)?; |
c1a9b12d SL |
459 | let child_name = match child.name() { |
460 | Some(s) => s, | |
461 | None => continue, | |
462 | }; | |
463 | if removals.iter().any(|r| r == child_name) { | |
464 | continue | |
465 | } | |
466 | ||
54a0048b | 467 | let name = CString::new(child_name)?; |
e9174d1e | 468 | members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
469 | name.as_ptr(), |
470 | child.raw())); | |
471 | strings.push(name); | |
472 | } | |
473 | } | |
474 | for addition in mem::replace(&mut self.additions, Vec::new()) { | |
475 | match addition { | |
476 | Addition::File { path, name_in_archive } => { | |
54a0048b SL |
477 | let path = CString::new(path.to_str().unwrap())?; |
478 | let name = CString::new(name_in_archive)?; | |
c1a9b12d SL |
479 | members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(), |
480 | name.as_ptr(), | |
e9174d1e | 481 | ptr::null_mut())); |
c1a9b12d SL |
482 | strings.push(path); |
483 | strings.push(name); | |
484 | } | |
485 | Addition::Archive { archive, archive_name: _, mut skip } => { | |
7453a54e | 486 | for child in archive.iter() { |
54a0048b | 487 | let child = child.map_err(string_to_io_error)?; |
7453a54e SL |
488 | if !is_relevant_child(&child) { |
489 | continue | |
490 | } | |
c1a9b12d | 491 | let child_name = child.name().unwrap(); |
7453a54e SL |
492 | if skip(child_name) { |
493 | continue | |
494 | } | |
495 | ||
496 | // It appears that LLVM's archive writer is a little | |
497 | // buggy if the name we pass down isn't just the | |
498 | // filename component, so chop that off here and | |
499 | // pass it in. | |
500 | // | |
501 | // See LLVM bug 25877 for more info. | |
502 | let child_name = Path::new(child_name) | |
503 | .file_name().unwrap() | |
504 | .to_str().unwrap(); | |
54a0048b | 505 | let name = CString::new(child_name)?; |
e9174d1e | 506 | let m = llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
507 | name.as_ptr(), |
508 | child.raw()); | |
509 | members.push(m); | |
510 | strings.push(name); | |
511 | } | |
512 | archives.push(archive); | |
513 | } | |
514 | } | |
515 | } | |
516 | ||
517 | let dst = self.config.dst.to_str().unwrap().as_bytes(); | |
54a0048b | 518 | let dst = CString::new(dst)?; |
c1a9b12d SL |
519 | let r = llvm::LLVMRustWriteArchive(dst.as_ptr(), |
520 | members.len() as libc::size_t, | |
521 | members.as_ptr(), | |
522 | self.should_update_symbols, | |
523 | kind); | |
524 | let ret = if r != 0 { | |
525 | let err = llvm::LLVMRustGetLastError(); | |
526 | let msg = if err.is_null() { | |
527 | "failed to write archive".to_string() | |
528 | } else { | |
529 | String::from_utf8_lossy(CStr::from_ptr(err).to_bytes()) | |
530 | .into_owned() | |
531 | }; | |
532 | Err(io::Error::new(io::ErrorKind::Other, msg)) | |
533 | } else { | |
534 | Ok(()) | |
535 | }; | |
536 | for member in members { | |
537 | llvm::LLVMRustArchiveMemberFree(member); | |
538 | } | |
539 | return ret | |
540 | } | |
541 | } | |
542 | } | |
7453a54e SL |
543 | |
544 | fn string_to_io_error(s: String) -> io::Error { | |
545 | io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) | |
546 | } |