]> git.lizzy.rs Git - rust.git/blob - src/librustc_back/archive.rs
use slicing sugar
[rust.git] / src / librustc_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::io::fs::PathExtensions;
14 use std::io::process::{Command, ProcessOutput};
15 use std::io::{fs, TempDir};
16 use std::io;
17 use std::os;
18 use std::str;
19 use syntax::diagnostic::Handler as ErrorHandler;
20
21 pub static METADATA_FILENAME: &'static str = "rust.metadata.bin";
22
23 pub struct ArchiveConfig<'a> {
24     pub handler: &'a ErrorHandler,
25     pub dst: Path,
26     pub lib_search_paths: Vec<Path>,
27     pub slib_prefix: String,
28     pub slib_suffix: String,
29     pub maybe_ar_prog: Option<String>
30 }
31
32 pub struct Archive<'a> {
33     handler: &'a ErrorHandler,
34     dst: Path,
35     lib_search_paths: Vec<Path>,
36     slib_prefix: String,
37     slib_suffix: String,
38     maybe_ar_prog: Option<String>
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     archive: Archive<'a>,
46     work_dir: TempDir,
47     /// Filename of each member that should be added to the archive.
48     members: Vec<Path>,
49     should_update_symbols: bool,
50 }
51
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[],
57         None => "ar"
58     };
59     let mut cmd = Command::new(ar);
60
61     cmd.arg(args).args(paths);
62     debug!("{}", cmd);
63
64     match cwd {
65         Some(p) => {
66             cmd.cwd(p);
67             debug!("inside {:?}", p.display());
68         }
69         None => {}
70     }
71
72     match cmd.spawn() {
73         Ok(prog) => {
74             let o = prog.wait_with_output().unwrap();
75             if !o.status.success() {
76                 handler.err(&format!("{} failed with: {}",
77                                  cmd,
78                                  o.status)[]);
79                 handler.note(&format!("stdout ---\n{}",
80                                   str::from_utf8(&o.output[]).unwrap())[]);
81                 handler.note(&format!("stderr ---\n{}",
82                                   str::from_utf8(&o.error[]).unwrap())
83                              []);
84                 handler.abort_if_errors();
85             }
86             o
87         },
88         Err(e) => {
89             handler.err(&format!("could not exec `{}`: {}", &ar[],
90                              e)[]);
91             handler.abort_if_errors();
92             panic!("rustc::back::archive::run_ar() should not reach this point");
93         }
94     }
95 }
96
97 pub fn find_library(name: &str, osprefix: &str, ossuffix: &str,
98                     search_paths: &[Path], handler: &ErrorHandler) -> Path {
99     // On Windows, static libraries sometimes show up as libfoo.a and other
100     // times show up as foo.lib
101     let oslibname = format!("{}{}{}", osprefix, name, ossuffix);
102     let unixlibname = format!("lib{}.a", name);
103
104     for path in search_paths.iter() {
105         debug!("looking for {} inside {:?}", name, path.display());
106         let test = path.join(&oslibname[]);
107         if test.exists() { return test }
108         if oslibname != unixlibname {
109             let test = path.join(&unixlibname[]);
110             if test.exists() { return test }
111         }
112     }
113     handler.fatal(&format!("could not find native static library `{}`, \
114                            perhaps an -L flag is missing?",
115                           name)[]);
116 }
117
118 impl<'a> Archive<'a> {
119     fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
120         let ArchiveConfig { handler, dst, lib_search_paths, slib_prefix, slib_suffix,
121             maybe_ar_prog } = config;
122         Archive {
123             handler: handler,
124             dst: dst,
125             lib_search_paths: lib_search_paths,
126             slib_prefix: slib_prefix,
127             slib_suffix: slib_suffix,
128             maybe_ar_prog: maybe_ar_prog
129         }
130     }
131
132     /// Opens an existing static archive
133     pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
134         let archive = Archive::new(config);
135         assert!(archive.dst.exists());
136         archive
137     }
138
139     /// Removes a file from this archive
140     pub fn remove_file(&mut self, file: &str) {
141         run_ar(self.handler, &self.maybe_ar_prog, "d", None, &[&self.dst, &Path::new(file)]);
142     }
143
144     /// Lists all files in an archive
145     pub fn files(&self) -> Vec<String> {
146         let output = run_ar(self.handler, &self.maybe_ar_prog, "t", None, &[&self.dst]);
147         let output = str::from_utf8(&output.output[]).unwrap();
148         // use lines_any because windows delimits output with `\r\n` instead of
149         // just `\n`
150         output.lines_any().map(|s| s.to_string()).collect()
151     }
152
153     /// Creates an `ArchiveBuilder` for adding files to this archive.
154     pub fn extend(self) -> ArchiveBuilder<'a> {
155         ArchiveBuilder::new(self)
156     }
157 }
158
159 impl<'a> ArchiveBuilder<'a> {
160     fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
161         ArchiveBuilder {
162             archive: archive,
163             work_dir: TempDir::new("rsar").unwrap(),
164             members: vec![],
165             should_update_symbols: false,
166         }
167     }
168
169     /// Create a new static archive, ready for adding files.
170     pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
171         let archive = Archive::new(config);
172         ArchiveBuilder::new(archive)
173     }
174
175     /// Adds all of the contents of a native library to this archive. This will
176     /// search in the relevant locations for a library named `name`.
177     pub fn add_native_library(&mut self, name: &str) -> io::IoResult<()> {
178         let location = find_library(name,
179                                     &self.archive.slib_prefix[],
180                                     &self.archive.slib_suffix[],
181                                     &self.archive.lib_search_paths[],
182                                     self.archive.handler);
183         self.add_archive(&location, name, |_| false)
184     }
185
186     /// Adds all of the contents of the rlib at the specified path to this
187     /// archive.
188     ///
189     /// This ignores adding the bytecode from the rlib, and if LTO is enabled
190     /// then the object file also isn't added.
191     pub fn add_rlib(&mut self, rlib: &Path, name: &str,
192                     lto: bool) -> io::IoResult<()> {
193         // Ignoring obj file starting with the crate name
194         // as simple comparison is not enough - there
195         // might be also an extra name suffix
196         let obj_start = format!("{}", name);
197         let obj_start = &obj_start[];
198         // Ignoring all bytecode files, no matter of
199         // name
200         let bc_ext = ".bytecode.deflate";
201
202         self.add_archive(rlib, &name[], |fname: &str| {
203             let skip_obj = lto && fname.starts_with(obj_start)
204                 && fname.ends_with(".o");
205             skip_obj || fname.ends_with(bc_ext) || fname == METADATA_FILENAME
206         })
207     }
208
209     /// Adds an arbitrary file to this archive
210     pub fn add_file(&mut self, file: &Path) -> io::IoResult<()> {
211         let filename = Path::new(file.filename().unwrap());
212         let new_file = self.work_dir.path().join(&filename);
213         try!(fs::copy(file, &new_file));
214         self.members.push(filename);
215         Ok(())
216     }
217
218     /// Indicate that the next call to `build` should updates all symbols in
219     /// the archive (run 'ar s' over it).
220     pub fn update_symbols(&mut self) {
221         self.should_update_symbols = true;
222     }
223
224     /// Combine the provided files, rlibs, and native libraries into a single
225     /// `Archive`.
226     pub fn build(self) -> Archive<'a> {
227         // Get an absolute path to the destination, so `ar` will work even
228         // though we run it from `self.work_dir`.
229         let abs_dst = os::getcwd().unwrap().join(&self.archive.dst);
230         assert!(!abs_dst.is_relative());
231         let mut args = vec![&abs_dst];
232         let mut total_len = abs_dst.as_vec().len();
233
234         if self.members.is_empty() {
235             // OSX `ar` does not allow using `r` with no members, but it does
236             // allow running `ar s file.a` to update symbols only.
237             if self.should_update_symbols {
238                 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
239                        "s", Some(self.work_dir.path()), &args[]);
240             }
241             return self.archive;
242         }
243
244         // Don't allow the total size of `args` to grow beyond 32,000 bytes.
245         // Windows will raise an error if the argument string is longer than
246         // 32,768, and we leave a bit of extra space for the program name.
247         static ARG_LENGTH_LIMIT: uint = 32000;
248
249         for member_name in self.members.iter() {
250             let len = member_name.as_vec().len();
251
252             // `len + 1` to account for the space that's inserted before each
253             // argument.  (Windows passes command-line arguments as a single
254             // string, not an array of strings.)
255             if total_len + len + 1 > ARG_LENGTH_LIMIT {
256                 // Add the archive members seen so far, without updating the
257                 // symbol table (`S`).
258                 run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
259                        "cruS", Some(self.work_dir.path()), &args[]);
260
261                 args.clear();
262                 args.push(&abs_dst);
263                 total_len = abs_dst.as_vec().len();
264             }
265
266             args.push(member_name);
267             total_len += len + 1;
268         }
269
270         // Add the remaining archive members, and update the symbol table if
271         // necessary.
272         let flags = if self.should_update_symbols { "crus" } else { "cruS" };
273         run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
274                flags, Some(self.work_dir.path()), &args[]);
275
276         self.archive
277     }
278
279     fn add_archive<F>(&mut self, archive: &Path, name: &str, mut skip: F) -> io::IoResult<()> where
280         F: FnMut(&str) -> bool,
281     {
282         let loc = TempDir::new("rsar").unwrap();
283
284         // First, extract the contents of the archive to a temporary directory.
285         // We don't unpack directly into `self.work_dir` due to the possibility
286         // of filename collisions.
287         let archive = os::make_absolute(archive).unwrap();
288         run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
289                "x", Some(loc.path()), &[&archive]);
290
291         // Next, we must rename all of the inputs to "guaranteed unique names".
292         // We move each file into `self.work_dir` under its new unique name.
293         // The reason for this renaming is that archives are keyed off the name
294         // of the files, so if two files have the same name they will override
295         // one another in the archive (bad).
296         //
297         // We skip any files explicitly desired for skipping, and we also skip
298         // all SYMDEF files as these are just magical placeholders which get
299         // re-created when we make a new archive anyway.
300         let files = try!(fs::readdir(loc.path()));
301         for file in files.iter() {
302             let filename = file.filename_str().unwrap();
303             if skip(filename) { continue }
304             if filename.contains(".SYMDEF") { continue }
305
306             let filename = format!("r-{}-{}", name, filename);
307             // LLDB (as mentioned in back::link) crashes on filenames of exactly
308             // 16 bytes in length. If we're including an object file with
309             // exactly 16-bytes of characters, give it some prefix so that it's
310             // not 16 bytes.
311             let filename = if filename.len() == 16 {
312                 format!("lldb-fix-{}", filename)
313             } else {
314                 filename
315             };
316             let new_filename = self.work_dir.path().join(&filename[]);
317             try!(fs::rename(file, &new_filename));
318             self.members.push(Path::new(filename));
319         }
320         Ok(())
321     }
322 }
323