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