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
13 use std::old_io::fs::PathExtensions;
14 use std::old_io::process::{Command, ProcessOutput};
15 use std::old_io::{fs, TempDir};
19 use syntax::diagnostic::Handler as ErrorHandler;
21 pub static METADATA_FILENAME: &'static str = "rust.metadata.bin";
23 pub struct ArchiveConfig<'a> {
24 pub handler: &'a ErrorHandler,
26 pub lib_search_paths: Vec<Path>,
27 pub slib_prefix: String,
28 pub slib_suffix: String,
29 pub maybe_ar_prog: Option<String>
32 pub struct Archive<'a> {
33 handler: &'a ErrorHandler,
35 lib_search_paths: Vec<Path>,
38 maybe_ar_prog: Option<String>
41 /// Helper for adding many files to an archive with a single invocation of
43 #[must_use = "must call build() to finish building the archive"]
44 pub struct ArchiveBuilder<'a> {
47 /// Filename of each member that should be added to the archive.
49 should_update_symbols: bool,
52 fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
53 args: &str, cwd: Option<&Path>,
54 paths: &[&Path]) -> ProcessOutput {
55 let ar = match *maybe_ar_prog {
56 Some(ref ar) => &ar[..],
59 let mut cmd = Command::new(ar);
61 cmd.arg(args).args(paths);
67 debug!("inside {:?}", p.display());
74 let o = prog.wait_with_output().unwrap();
75 if !o.status.success() {
76 handler.err(&format!("{:?} failed with: {}", cmd, o.status)[]);
77 handler.note(&format!("stdout ---\n{}",
78 str::from_utf8(&o.output[]).unwrap())[]);
79 handler.note(&format!("stderr ---\n{}",
80 str::from_utf8(&o.error[]).unwrap())
82 handler.abort_if_errors();
87 handler.err(&format!("could not exec `{}`: {}", &ar[..],
89 handler.abort_if_errors();
90 panic!("rustc::back::archive::run_ar() should not reach this point");
95 pub fn find_library(name: &str, osprefix: &str, ossuffix: &str,
96 search_paths: &[Path], handler: &ErrorHandler) -> Path {
97 // On Windows, static libraries sometimes show up as libfoo.a and other
98 // times show up as foo.lib
99 let oslibname = format!("{}{}{}", osprefix, name, ossuffix);
100 let unixlibname = format!("lib{}.a", name);
102 for path in search_paths {
103 debug!("looking for {} inside {:?}", name, path.display());
104 let test = path.join(&oslibname[..]);
105 if test.exists() { return test }
106 if oslibname != unixlibname {
107 let test = path.join(&unixlibname[..]);
108 if test.exists() { return test }
111 handler.fatal(&format!("could not find native static library `{}`, \
112 perhaps an -L flag is missing?",
116 impl<'a> Archive<'a> {
117 fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
118 let ArchiveConfig { handler, dst, lib_search_paths, slib_prefix, slib_suffix,
119 maybe_ar_prog } = config;
123 lib_search_paths: lib_search_paths,
124 slib_prefix: slib_prefix,
125 slib_suffix: slib_suffix,
126 maybe_ar_prog: maybe_ar_prog
130 /// Opens an existing static archive
131 pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
132 let archive = Archive::new(config);
133 assert!(archive.dst.exists());
137 /// Removes a file from this archive
138 pub fn remove_file(&mut self, file: &str) {
139 run_ar(self.handler, &self.maybe_ar_prog, "d", None, &[&self.dst, &Path::new(file)]);
142 /// Lists all files in an archive
143 pub fn files(&self) -> Vec<String> {
144 let output = run_ar(self.handler, &self.maybe_ar_prog, "t", None, &[&self.dst]);
145 let output = str::from_utf8(&output.output[]).unwrap();
146 // use lines_any because windows delimits output with `\r\n` instead of
148 output.lines_any().map(|s| s.to_string()).collect()
151 /// Creates an `ArchiveBuilder` for adding files to this archive.
152 pub fn extend(self) -> ArchiveBuilder<'a> {
153 ArchiveBuilder::new(self)
157 impl<'a> ArchiveBuilder<'a> {
158 fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
161 work_dir: TempDir::new("rsar").unwrap(),
163 should_update_symbols: false,
167 /// Create a new static archive, ready for adding files.
168 pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
169 let archive = Archive::new(config);
170 ArchiveBuilder::new(archive)
173 /// Adds all of the contents of a native library to this archive. This will
174 /// search in the relevant locations for a library named `name`.
175 pub fn add_native_library(&mut self, name: &str) -> old_io::IoResult<()> {
176 let location = find_library(name,
177 &self.archive.slib_prefix[],
178 &self.archive.slib_suffix[],
179 &self.archive.lib_search_paths[],
180 self.archive.handler);
181 self.add_archive(&location, name, |_| false)
184 /// Adds all of the contents of the rlib at the specified path to this
187 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
188 /// then the object file also isn't added.
189 pub fn add_rlib(&mut self, rlib: &Path, name: &str,
190 lto: bool) -> old_io::IoResult<()> {
191 // Ignoring obj file starting with the crate name
192 // as simple comparison is not enough - there
193 // might be also an extra name suffix
194 let obj_start = format!("{}", name);
195 let obj_start = &obj_start[..];
196 // Ignoring all bytecode files, no matter of
198 let bc_ext = ".bytecode.deflate";
200 self.add_archive(rlib, &name[..], |fname: &str| {
201 let skip_obj = lto && fname.starts_with(obj_start)
202 && fname.ends_with(".o");
203 skip_obj || fname.ends_with(bc_ext) || fname == METADATA_FILENAME
207 /// Adds an arbitrary file to this archive
208 pub fn add_file(&mut self, file: &Path) -> old_io::IoResult<()> {
209 let filename = Path::new(file.filename().unwrap());
210 let new_file = self.work_dir.path().join(&filename);
211 try!(fs::copy(file, &new_file));
212 self.members.push(filename);
216 /// Indicate that the next call to `build` should updates all symbols in
217 /// the archive (run 'ar s' over it).
218 pub fn update_symbols(&mut self) {
219 self.should_update_symbols = true;
222 /// Combine the provided files, rlibs, and native libraries into a single
224 pub fn build(self) -> Archive<'a> {
225 // Get an absolute path to the destination, so `ar` will work even
226 // though we run it from `self.work_dir`.
227 let abs_dst = env::current_dir().unwrap().join(&self.archive.dst);
228 assert!(!abs_dst.is_relative());
229 let mut args = vec![&abs_dst];
230 let mut total_len = abs_dst.as_vec().len();
232 if self.members.is_empty() {
233 // OSX `ar` does not allow using `r` with no members, but it does
234 // allow running `ar s file.a` to update symbols only.
235 if self.should_update_symbols {
236 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
237 "s", Some(self.work_dir.path()), &args[..]);
242 // Don't allow the total size of `args` to grow beyond 32,000 bytes.
243 // Windows will raise an error if the argument string is longer than
244 // 32,768, and we leave a bit of extra space for the program name.
245 static ARG_LENGTH_LIMIT: uint = 32000;
247 for member_name in &self.members {
248 let len = member_name.as_vec().len();
250 // `len + 1` to account for the space that's inserted before each
251 // argument. (Windows passes command-line arguments as a single
252 // string, not an array of strings.)
253 if total_len + len + 1 > ARG_LENGTH_LIMIT {
254 // Add the archive members seen so far, without updating the
255 // symbol table (`S`).
256 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
257 "cruS", Some(self.work_dir.path()), &args[..]);
261 total_len = abs_dst.as_vec().len();
264 args.push(member_name);
265 total_len += len + 1;
268 // Add the remaining archive members, and update the symbol table if
270 let flags = if self.should_update_symbols { "crus" } else { "cruS" };
271 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
272 flags, Some(self.work_dir.path()), &args[..]);
277 fn add_archive<F>(&mut self, archive: &Path, name: &str,
278 mut skip: F) -> old_io::IoResult<()>
279 where F: FnMut(&str) -> bool,
281 let loc = TempDir::new("rsar").unwrap();
283 // First, extract the contents of the archive to a temporary directory.
284 // We don't unpack directly into `self.work_dir` due to the possibility
285 // of filename collisions.
286 let archive = env::current_dir().unwrap().join(archive);
287 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
288 "x", Some(loc.path()), &[&archive]);
290 // Next, we must rename all of the inputs to "guaranteed unique names".
291 // We move 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 let files = try!(fs::readdir(loc.path()));
301 let filename = file.filename_str().unwrap();
302 if skip(filename) { continue }
303 if filename.contains(".SYMDEF") { continue }
305 let filename = format!("r-{}-{}", name, filename);
306 // LLDB (as mentioned in back::link) crashes on filenames of exactly
307 // 16 bytes in length. If we're including an object file with
308 // exactly 16-bytes of characters, give it some prefix so that it's
310 let filename = if filename.len() == 16 {
311 format!("lldb-fix-{}", filename)
315 let new_filename = self.work_dir.path().join(&filename[..]);
316 try!(fs::rename(file, &new_filename));
317 self.members.push(Path::new(filename));