]> git.proxmox.com Git - rustc.git/blob - src/librustc_trans/back/archive.rs
Imported Upstream version 1.6.0+dfsg1
[rustc.git] / src / librustc_trans / back / archive.rs
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};
21 use std::ptr;
22 use std::str;
23
24 use middle::cstore::CrateStore;
25
26 use libc;
27 use llvm::archive_ro::{ArchiveRO, Child};
28 use llvm::{self, ArchiveKind};
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()
127 .filter(is_relevant_child)
128 .filter_map(|child| child.name())
129 .filter(|name| !self.removals.iter().any(|x| x == name))
130 .map(|name| name.to_string())
131 .collect();
132 return ret;
133 }
134
135 fn src_archive(&mut self) -> Option<&ArchiveRO> {
136 if let Some(ref a) = self.src_archive {
137 return a.as_ref()
138 }
139 let src = match self.config.src {
140 Some(ref src) => src,
141 None => return None,
142 };
143 self.src_archive = Some(ArchiveRO::open(src));
144 self.src_archive.as_ref().unwrap().as_ref()
145 }
146
147 /// Adds all of the contents of a native library to this archive. This will
148 /// search in the relevant locations for a library named `name`.
149 pub fn add_native_library(&mut self, name: &str) {
150 let location = find_library(name, &self.config.lib_search_paths,
151 self.config.sess);
152 self.add_archive(&location, name, |_| false).unwrap_or_else(|e| {
153 self.config.sess.fatal(&format!("failed to add native library {}: {}",
154 location.to_string_lossy(), e));
155 });
156 }
157
158 /// Adds all of the contents of the rlib at the specified path to this
159 /// archive.
160 ///
161 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
162 /// then the object file also isn't added.
163 pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool)
164 -> io::Result<()> {
165 // Ignoring obj file starting with the crate name
166 // as simple comparison is not enough - there
167 // might be also an extra name suffix
168 let obj_start = format!("{}", name);
169
170 // Ignoring all bytecode files, no matter of
171 // name
172 let bc_ext = ".bytecode.deflate";
173 let metadata_filename =
174 self.config.sess.cstore.metadata_filename().to_owned();
175
176 self.add_archive(rlib, &name[..], move |fname: &str| {
177 let skip_obj = lto && fname.starts_with(&obj_start)
178 && fname.ends_with(".o");
179 skip_obj || fname.ends_with(bc_ext) || fname == metadata_filename
180 })
181 }
182
183 fn add_archive<F>(&mut self, archive: &Path, name: &str, skip: F)
184 -> io::Result<()>
185 where F: FnMut(&str) -> bool + 'static
186 {
187 let archive = match ArchiveRO::open(archive) {
188 Some(ar) => ar,
189 None => return Err(io::Error::new(io::ErrorKind::Other,
190 "failed to open archive")),
191 };
192 self.additions.push(Addition::Archive {
193 archive: archive,
194 archive_name: name.to_string(),
195 skip: Box::new(skip),
196 });
197 Ok(())
198 }
199
200 /// Adds an arbitrary file to this archive
201 pub fn add_file(&mut self, file: &Path) {
202 let name = file.file_name().unwrap().to_str().unwrap();
203 self.additions.push(Addition::File {
204 path: file.to_path_buf(),
205 name_in_archive: name.to_string(),
206 });
207 }
208
209 /// Indicate that the next call to `build` should updates all symbols in
210 /// the archive (run 'ar s' over it).
211 pub fn update_symbols(&mut self) {
212 self.should_update_symbols = true;
213 }
214
215 /// Combine the provided files, rlibs, and native libraries into a single
216 /// `Archive`.
217 pub fn build(&mut self) {
218 let res = match self.llvm_archive_kind() {
219 Some(kind) => self.build_with_llvm(kind),
220 None => self.build_with_ar_cmd(),
221 };
222 if let Err(e) = res {
223 self.config.sess.fatal(&format!("failed to build archive: {}", e));
224 }
225 }
226
227 pub fn llvm_archive_kind(&self) -> Option<ArchiveKind> {
228 if unsafe { llvm::LLVMVersionMinor() < 7 } {
229 return None
230 }
231
232 // Currently LLVM only supports writing archives in the 'gnu' format.
233 match &self.config.sess.target.target.options.archive_format[..] {
234 "gnu" => Some(ArchiveKind::K_GNU),
235 "mips64" => Some(ArchiveKind::K_MIPS64),
236 "bsd" => Some(ArchiveKind::K_BSD),
237 "coff" => Some(ArchiveKind::K_COFF),
238 _ => None,
239 }
240 }
241
242 pub fn using_llvm(&self) -> bool {
243 self.llvm_archive_kind().is_some()
244 }
245
246 fn build_with_ar_cmd(&mut self) -> io::Result<()> {
247 let removals = mem::replace(&mut self.removals, Vec::new());
248 let additions = mem::replace(&mut self.additions, Vec::new());
249 let should_update_symbols = mem::replace(&mut self.should_update_symbols,
250 false);
251
252 // Don't use fs::copy because libs may be installed as read-only and we
253 // want to modify this archive, so we use `io::copy` to not preserve
254 // permission bits.
255 if let Some(ref s) = self.config.src {
256 try!(io::copy(&mut try!(File::open(s)),
257 &mut try!(File::create(&self.config.dst))));
258 }
259
260 if removals.len() > 0 {
261 self.run(None, Action::Remove(&removals));
262 }
263
264 let mut members = Vec::new();
265 for addition in additions {
266 match addition {
267 Addition::File { path, name_in_archive } => {
268 let dst = self.work_dir.path().join(&name_in_archive);
269 try!(fs::copy(&path, &dst));
270 members.push(PathBuf::from(name_in_archive));
271 }
272 Addition::Archive { archive, archive_name, mut skip } => {
273 try!(self.add_archive_members(&mut members, archive,
274 &archive_name, &mut *skip));
275 }
276 }
277 }
278
279 // Get an absolute path to the destination, so `ar` will work even
280 // though we run it from `self.work_dir`.
281 let mut objects = Vec::new();
282 let mut total_len = self.config.dst.to_string_lossy().len();
283
284 if members.is_empty() {
285 if should_update_symbols {
286 self.run(Some(self.work_dir.path()), Action::UpdateSymbols);
287 }
288 return Ok(())
289 }
290
291 // Don't allow the total size of `args` to grow beyond 32,000 bytes.
292 // Windows will raise an error if the argument string is longer than
293 // 32,768, and we leave a bit of extra space for the program name.
294 const ARG_LENGTH_LIMIT: usize = 32_000;
295
296 for member_name in &members {
297 let len = member_name.to_string_lossy().len();
298
299 // `len + 1` to account for the space that's inserted before each
300 // argument. (Windows passes command-line arguments as a single
301 // string, not an array of strings.)
302 if total_len + len + 1 > ARG_LENGTH_LIMIT {
303 // Add the archive members seen so far, without updating the
304 // symbol table.
305 self.run(Some(self.work_dir.path()),
306 Action::AddObjects(&objects, false));
307
308 objects.clear();
309 total_len = self.config.dst.to_string_lossy().len();
310 }
311
312 objects.push(member_name);
313 total_len += len + 1;
314 }
315
316 // Add the remaining archive members, and update the symbol table if
317 // necessary.
318 self.run(Some(self.work_dir.path()),
319 Action::AddObjects(&objects, should_update_symbols));
320 Ok(())
321 }
322
323 fn add_archive_members(&mut self, members: &mut Vec<PathBuf>,
324 archive: ArchiveRO, name: &str,
325 skip: &mut FnMut(&str) -> bool) -> io::Result<()> {
326 // Next, we must rename all of the inputs to "guaranteed unique names".
327 // We write each file into `self.work_dir` under its new unique name.
328 // The reason for this renaming is that archives are keyed off the name
329 // of the files, so if two files have the same name they will override
330 // one another in the archive (bad).
331 //
332 // We skip any files explicitly desired for skipping, and we also skip
333 // all SYMDEF files as these are just magical placeholders which get
334 // re-created when we make a new archive anyway.
335 for file in archive.iter().filter(is_relevant_child) {
336 let filename = file.name().unwrap();
337 if skip(filename) { continue }
338 let filename = Path::new(filename).file_name().unwrap()
339 .to_str().unwrap();
340
341 // Archives on unix systems typically do not have slashes in
342 // filenames as the `ar` utility generally only uses the last
343 // component of a path for the filename list in the archive. On
344 // Windows, however, archives assembled with `lib.exe` will preserve
345 // the full path to the file that was placed in the archive,
346 // including path separators.
347 //
348 // The code below is munging paths so it'll go wrong pretty quickly
349 // if there's some unexpected slashes in the filename, so here we
350 // just chop off everything but the filename component. Note that
351 // this can cause duplicate filenames, but that's also handled below
352 // as well.
353 let filename = Path::new(filename).file_name().unwrap()
354 .to_str().unwrap();
355
356 // An archive can contain files of the same name multiple times, so
357 // we need to be sure to not have them overwrite one another when we
358 // extract them. Consequently we need to find a truly unique file
359 // name for us!
360 let mut new_filename = String::new();
361 for n in 0.. {
362 let n = if n == 0 {String::new()} else {format!("-{}", n)};
363 new_filename = format!("r{}-{}-{}", n, name, filename);
364
365 // LLDB (as mentioned in back::link) crashes on filenames of
366 // exactly
367 // 16 bytes in length. If we're including an object file with
368 // exactly 16-bytes of characters, give it some prefix so
369 // that it's not 16 bytes.
370 new_filename = if new_filename.len() == 16 {
371 format!("lldb-fix-{}", new_filename)
372 } else {
373 new_filename
374 };
375
376 let present = members.iter().filter_map(|p| {
377 p.file_name().and_then(|f| f.to_str())
378 }).any(|s| s == new_filename);
379 if !present {
380 break
381 }
382 }
383 let dst = self.work_dir.path().join(&new_filename);
384 try!(try!(File::create(&dst)).write_all(file.data()));
385 members.push(PathBuf::from(new_filename));
386 }
387 Ok(())
388 }
389
390 fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
391 let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
392 let ar = &self.config.ar_prog;
393 let mut cmd = Command::new(ar);
394 cmd.env("PATH", &self.config.command_path);
395 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
396 self.prepare_ar_action(&mut cmd, &abs_dst, action);
397 info!("{:?}", cmd);
398
399 if let Some(p) = cwd {
400 cmd.current_dir(p);
401 info!("inside {:?}", p.display());
402 }
403
404 let sess = &self.config.sess;
405 match cmd.spawn() {
406 Ok(prog) => {
407 let o = prog.wait_with_output().unwrap();
408 if !o.status.success() {
409 sess.err(&format!("{:?} failed with: {}", cmd, o.status));
410 sess.note(&format!("stdout ---\n{}",
411 str::from_utf8(&o.stdout).unwrap()));
412 sess.note(&format!("stderr ---\n{}",
413 str::from_utf8(&o.stderr).unwrap()));
414 sess.abort_if_errors();
415 }
416 o
417 },
418 Err(e) => {
419 sess.fatal(&format!("could not exec `{}`: {}",
420 self.config.ar_prog, e));
421 }
422 }
423 }
424
425 fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
426 match action {
427 Action::Remove(files) => {
428 cmd.arg("d").arg(dst).args(files);
429 }
430 Action::AddObjects(objs, update_symbols) => {
431 cmd.arg(if update_symbols {"crs"} else {"crS"})
432 .arg(dst)
433 .args(objs);
434 }
435 Action::UpdateSymbols => {
436 cmd.arg("s").arg(dst);
437 }
438 }
439 }
440
441 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
442 let mut archives = Vec::new();
443 let mut strings = Vec::new();
444 let mut members = Vec::new();
445 let removals = mem::replace(&mut self.removals, Vec::new());
446
447 unsafe {
448 if let Some(archive) = self.src_archive() {
449 for child in archive.iter() {
450 let child_name = match child.name() {
451 Some(s) => s,
452 None => continue,
453 };
454 if removals.iter().any(|r| r == child_name) {
455 continue
456 }
457
458 let name = try!(CString::new(child_name));
459 members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
460 name.as_ptr(),
461 child.raw()));
462 strings.push(name);
463 }
464 }
465 for addition in mem::replace(&mut self.additions, Vec::new()) {
466 match addition {
467 Addition::File { path, name_in_archive } => {
468 let path = try!(CString::new(path.to_str().unwrap()));
469 let name = try!(CString::new(name_in_archive));
470 members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
471 name.as_ptr(),
472 ptr::null_mut()));
473 strings.push(path);
474 strings.push(name);
475 }
476 Addition::Archive { archive, archive_name: _, mut skip } => {
477 for child in archive.iter().filter(is_relevant_child) {
478 let child_name = child.name().unwrap();
479 if skip(child_name) { continue }
480
481 let name = try!(CString::new(child_name));
482 let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
483 name.as_ptr(),
484 child.raw());
485 members.push(m);
486 strings.push(name);
487 }
488 archives.push(archive);
489 }
490 }
491 }
492
493 let dst = self.config.dst.to_str().unwrap().as_bytes();
494 let dst = try!(CString::new(dst));
495 let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
496 members.len() as libc::size_t,
497 members.as_ptr(),
498 self.should_update_symbols,
499 kind);
500 let ret = if r != 0 {
501 let err = llvm::LLVMRustGetLastError();
502 let msg = if err.is_null() {
503 "failed to write archive".to_string()
504 } else {
505 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
506 .into_owned()
507 };
508 Err(io::Error::new(io::ErrorKind::Other, msg))
509 } else {
510 Ok(())
511 };
512 for member in members {
513 llvm::LLVMRustArchiveMemberFree(member);
514 }
515 return ret
516 }
517 }
518 }