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.
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.
11 //! A helper class for dealing with static archives
14 use std::fs::{self, File};
15 use std::io::prelude::*;
17 use std::path::{Path, PathBuf};
18 use std::process::{Command, Output, Stdio};
20 use syntax::diagnostic::Handler as ErrorHandler;
21 use rustc_llvm::archive_ro::ArchiveRO;
25 pub const METADATA_FILENAME: &'static str = "rust.metadata.bin";
27 pub struct ArchiveConfig<'a> {
28 pub handler: &'a ErrorHandler,
30 pub lib_search_paths: Vec<PathBuf>,
31 pub slib_prefix: String,
32 pub slib_suffix: String,
36 pub struct Archive<'a> {
37 config: ArchiveConfig<'a>,
40 /// Helper for adding many files to an archive with a single invocation of
42 #[must_use = "must call build() to finish building the archive"]
43 pub struct ArchiveBuilder<'a> {
46 /// Filename of each member that should be added to the archive.
47 members: Vec<PathBuf>,
48 should_update_symbols: bool,
53 AddObjects(&'a [&'a PathBuf], bool),
57 pub fn find_library(name: &str, osprefix: &str, ossuffix: &str,
58 search_paths: &[PathBuf],
59 handler: &ErrorHandler) -> PathBuf {
60 // On Windows, static libraries sometimes show up as libfoo.a and other
61 // times show up as foo.lib
62 let oslibname = format!("{}{}{}", osprefix, name, ossuffix);
63 let unixlibname = format!("lib{}.a", name);
65 for path in search_paths {
66 debug!("looking for {} inside {:?}", name, path);
67 let test = path.join(&oslibname[..]);
68 if test.exists() { return test }
69 if oslibname != unixlibname {
70 let test = path.join(&unixlibname[..]);
71 if test.exists() { return test }
74 handler.fatal(&format!("could not find native static library `{}`, \
75 perhaps an -L flag is missing?",
79 impl<'a> Archive<'a> {
80 fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
81 Archive { config: config }
84 /// Opens an existing static archive
85 pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
86 let archive = Archive::new(config);
87 assert!(archive.config.dst.exists());
91 /// Removes a file from this archive
92 pub fn remove_file(&mut self, file: &str) {
93 self.run(None, Action::Remove(Path::new(file)));
96 /// Lists all files in an archive
97 pub fn files(&self) -> Vec<String> {
98 let archive = match ArchiveRO::open(&self.config.dst) {
100 None => return Vec::new(),
102 let ret = archive.iter().filter_map(|child| child.name())
103 .map(|name| name.to_string())
108 /// Creates an `ArchiveBuilder` for adding files to this archive.
109 pub fn extend(self) -> ArchiveBuilder<'a> {
110 ArchiveBuilder::new(self)
113 fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
114 let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
115 let ar = &self.config.ar_prog;
116 let mut cmd = Command::new(ar);
117 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
118 self.prepare_ar_action(&mut cmd, &abs_dst, action);
121 if let Some(p) = cwd {
123 info!("inside {:?}", p.display());
126 let handler = &self.config.handler;
129 let o = prog.wait_with_output().unwrap();
130 if !o.status.success() {
131 handler.err(&format!("{:?} failed with: {}", cmd, o.status));
132 handler.note(&format!("stdout ---\n{}",
133 str::from_utf8(&o.stdout).unwrap()));
134 handler.note(&format!("stderr ---\n{}",
135 str::from_utf8(&o.stderr).unwrap()));
136 handler.abort_if_errors();
141 handler.err(&format!("could not exec `{}`: {}",
142 self.config.ar_prog, e));
143 handler.abort_if_errors();
144 panic!("rustc::back::archive::run() should not reach this point");
149 fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
151 Action::Remove(file) => {
152 cmd.arg("d").arg(dst).arg(file);
154 Action::AddObjects(objs, update_symbols) => {
155 cmd.arg(if update_symbols {"crus"} else {"cruS"})
159 Action::UpdateSymbols => {
160 cmd.arg("s").arg(dst);
166 impl<'a> ArchiveBuilder<'a> {
167 fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
170 work_dir: TempDir::new("rsar").unwrap(),
172 should_update_symbols: false,
176 /// Create a new static archive, ready for adding files.
177 pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
178 let archive = Archive::new(config);
179 ArchiveBuilder::new(archive)
182 /// Adds all of the contents of a native library to this archive. This will
183 /// search in the relevant locations for a library named `name`.
184 pub fn add_native_library(&mut self, name: &str) -> io::Result<()> {
185 let location = find_library(name,
186 &self.archive.config.slib_prefix,
187 &self.archive.config.slib_suffix,
188 &self.archive.config.lib_search_paths,
189 self.archive.config.handler);
190 self.add_archive(&location, name, |_| false)
193 /// Adds all of the contents of the rlib at the specified path to this
196 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
197 /// then the object file also isn't added.
198 pub fn add_rlib(&mut self, rlib: &Path, name: &str,
199 lto: bool) -> io::Result<()> {
200 // Ignoring obj file starting with the crate name
201 // as simple comparison is not enough - there
202 // might be also an extra name suffix
203 let obj_start = format!("{}", name);
204 let obj_start = &obj_start[..];
205 // Ignoring all bytecode files, no matter of
207 let bc_ext = ".bytecode.deflate";
209 self.add_archive(rlib, &name[..], |fname: &str| {
210 let skip_obj = lto && fname.starts_with(obj_start)
211 && fname.ends_with(".o");
212 skip_obj || fname.ends_with(bc_ext) || fname == METADATA_FILENAME
216 /// Adds an arbitrary file to this archive
217 pub fn add_file(&mut self, file: &Path) -> io::Result<()> {
218 let filename = Path::new(file.file_name().unwrap());
219 let new_file = self.work_dir.path().join(&filename);
220 try!(fs::copy(file, &new_file));
221 self.members.push(filename.to_path_buf());
225 /// Indicate that the next call to `build` should updates all symbols in
226 /// the archive (run 'ar s' over it).
227 pub fn update_symbols(&mut self) {
228 self.should_update_symbols = true;
231 /// Combine the provided files, rlibs, and native libraries into a single
233 pub fn build(self) -> Archive<'a> {
234 // Get an absolute path to the destination, so `ar` will work even
235 // though we run it from `self.work_dir`.
236 let mut objects = Vec::new();
237 let mut total_len = self.archive.config.dst.to_string_lossy().len();
239 if self.members.is_empty() {
240 if self.should_update_symbols {
241 self.archive.run(Some(self.work_dir.path()),
242 Action::UpdateSymbols);
247 // Don't allow the total size of `args` to grow beyond 32,000 bytes.
248 // Windows will raise an error if the argument string is longer than
249 // 32,768, and we leave a bit of extra space for the program name.
250 const ARG_LENGTH_LIMIT: usize = 32_000;
252 for member_name in &self.members {
253 let len = member_name.to_string_lossy().len();
255 // `len + 1` to account for the space that's inserted before each
256 // argument. (Windows passes command-line arguments as a single
257 // string, not an array of strings.)
258 if total_len + len + 1 > ARG_LENGTH_LIMIT {
259 // Add the archive members seen so far, without updating the
261 self.archive.run(Some(self.work_dir.path()),
262 Action::AddObjects(&objects, false));
265 total_len = self.archive.config.dst.to_string_lossy().len();
268 objects.push(member_name);
269 total_len += len + 1;
272 // Add the remaining archive members, and update the symbol table if
274 self.archive.run(Some(self.work_dir.path()),
275 Action::AddObjects(&objects, self.should_update_symbols));
280 fn add_archive<F>(&mut self, archive: &Path, name: &str,
281 mut skip: F) -> io::Result<()>
282 where F: FnMut(&str) -> bool,
284 let archive = match ArchiveRO::open(archive) {
286 None => return Err(io::Error::new(io::ErrorKind::Other,
287 "failed to open archive")),
290 // Next, we must rename all of the inputs to "guaranteed unique names".
291 // We write each file into `self.work_dir` under its new unique name.
292 // The reason for this renaming is that archives are keyed off the name
293 // of the files, so if two files have the same name they will override
294 // one another in the archive (bad).
296 // We skip any files explicitly desired for skipping, and we also skip
297 // all SYMDEF files as these are just magical placeholders which get
298 // re-created when we make a new archive anyway.
299 for file in archive.iter() {
300 let filename = match file.name() {
304 if filename.contains(".SYMDEF") { continue }
305 if skip(filename) { continue }
306 let filename = Path::new(filename).file_name().unwrap()
309 // An archive can contain files of the same name multiple times, so
310 // we need to be sure to not have them overwrite one another when we
311 // extract them. Consequently we need to find a truly unique file
313 let mut new_filename = String::new();
315 let n = if n == 0 {String::new()} else {format!("-{}", n)};
316 new_filename = format!("r{}-{}-{}", n, name, filename);
318 // LLDB (as mentioned in back::link) crashes on filenames of
320 // 16 bytes in length. If we're including an object file with
321 // exactly 16-bytes of characters, give it some prefix so
322 // that it's not 16 bytes.
323 new_filename = if new_filename.len() == 16 {
324 format!("lldb-fix-{}", new_filename)
329 let present = self.members.iter().filter_map(|p| {
330 p.file_name().and_then(|f| f.to_str())
331 }).any(|s| s == new_filename);
336 let dst = self.work_dir.path().join(&new_filename);
337 try!(try!(File::create(&dst)).write_all(file.data()));
338 self.members.push(PathBuf::from(new_filename));