]> git.proxmox.com Git - rustc.git/blame - src/librustc_trans/back/archive.rs
Imported Upstream version 1.9.0+dfsg1
[rustc.git] / src / librustc_trans / back / archive.rs
CommitLineData
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
13use std::env;
14use std::ffi::{CString, CStr, OsString};
15use std::fs::{self, File};
16use std::io::prelude::*;
17use std::io;
18use std::mem;
19use std::path::{Path, PathBuf};
20use std::process::{Command, Output, Stdio};
e9174d1e 21use std::ptr;
c1a9b12d
SL
22use std::str;
23
92a42be0
SL
24use middle::cstore::CrateStore;
25
c1a9b12d
SL
26use libc;
27use llvm::archive_ro::{ArchiveRO, Child};
28use llvm::{self, ArchiveKind};
c1a9b12d
SL
29use rustc::session::Session;
30use rustc_back::tempdir::TempDir;
31
32pub 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"]
44pub 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
53enum 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
65enum Action<'a> {
66 Remove(&'a [String]),
67 AddObjects(&'a [&'a PathBuf], bool),
68 UpdateSymbols,
69}
70
71pub 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
94fn is_relevant_child(c: &Child) -> bool {
95 match c.name() {
96 Some(name) => !name.contains("SYMDEF"),
97 None => false,
98 }
99}
100
101impl<'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
544fn string_to_io_error(s: String) -> io::Error {
545 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
546}