]> git.lizzy.rs Git - rust.git/blob - src/librustc_back/archive.rs
Auto merge of #26884 - dotdash:fast, r=alexcrichton
[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::env;
14 use std::ffi::OsString;
15 use std::fs::{self, File};
16 use std::io::prelude::*;
17 use std::io;
18 use std::path::{Path, PathBuf};
19 use std::process::{Command, Output, Stdio};
20 use std::str;
21 use syntax::diagnostic::Handler as ErrorHandler;
22 use rustc_llvm::archive_ro::ArchiveRO;
23
24 use tempdir::TempDir;
25
26 pub const METADATA_FILENAME: &'static str = "rust.metadata.bin";
27
28 pub struct ArchiveConfig<'a> {
29     pub handler: &'a ErrorHandler,
30     pub dst: PathBuf,
31     pub lib_search_paths: Vec<PathBuf>,
32     pub slib_prefix: String,
33     pub slib_suffix: String,
34     pub ar_prog: String,
35     pub command_path: OsString,
36 }
37
38 pub struct Archive<'a> {
39     config: ArchiveConfig<'a>,
40 }
41
42 /// Helper for adding many files to an archive with a single invocation of
43 /// `ar`.
44 #[must_use = "must call build() to finish building the archive"]
45 pub struct ArchiveBuilder<'a> {
46     archive: Archive<'a>,
47     work_dir: TempDir,
48     /// Filename of each member that should be added to the archive.
49     members: Vec<PathBuf>,
50     should_update_symbols: bool,
51 }
52
53 enum Action<'a> {
54     Remove(&'a Path),
55     AddObjects(&'a [&'a PathBuf], bool),
56     UpdateSymbols,
57 }
58
59 pub fn find_library(name: &str, osprefix: &str, ossuffix: &str,
60                     search_paths: &[PathBuf],
61                     handler: &ErrorHandler) -> PathBuf {
62     // On Windows, static libraries sometimes show up as libfoo.a and other
63     // times show up as foo.lib
64     let oslibname = format!("{}{}{}", osprefix, name, ossuffix);
65     let unixlibname = format!("lib{}.a", name);
66
67     for path in search_paths {
68         debug!("looking for {} inside {:?}", name, path);
69         let test = path.join(&oslibname[..]);
70         if test.exists() { return test }
71         if oslibname != unixlibname {
72             let test = path.join(&unixlibname[..]);
73             if test.exists() { return test }
74         }
75     }
76     handler.fatal(&format!("could not find native static library `{}`, \
77                            perhaps an -L flag is missing?",
78                           name));
79 }
80
81 impl<'a> Archive<'a> {
82     fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
83         Archive { config: config }
84     }
85
86     /// Opens an existing static archive
87     pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
88         let archive = Archive::new(config);
89         assert!(archive.config.dst.exists());
90         archive
91     }
92
93     /// Removes a file from this archive
94     pub fn remove_file(&mut self, file: &str) {
95         self.run(None, Action::Remove(Path::new(file)));
96     }
97
98     /// Lists all files in an archive
99     pub fn files(&self) -> Vec<String> {
100         let archive = match ArchiveRO::open(&self.config.dst) {
101             Some(ar) => ar,
102             None => return Vec::new(),
103         };
104         let ret = archive.iter().filter_map(|child| child.name())
105                          .map(|name| name.to_string())
106                          .collect();
107         return ret;
108     }
109
110     /// Creates an `ArchiveBuilder` for adding files to this archive.
111     pub fn extend(self) -> ArchiveBuilder<'a> {
112         ArchiveBuilder::new(self)
113     }
114
115     fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
116         let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
117         let ar = &self.config.ar_prog;
118         let mut cmd = Command::new(ar);
119         cmd.env("PATH", &self.config.command_path);
120         cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
121         self.prepare_ar_action(&mut cmd, &abs_dst, action);
122         info!("{:?}", cmd);
123
124         if let Some(p) = cwd {
125             cmd.current_dir(p);
126             info!("inside {:?}", p.display());
127         }
128
129         let handler = &self.config.handler;
130         match cmd.spawn() {
131             Ok(prog) => {
132                 let o = prog.wait_with_output().unwrap();
133                 if !o.status.success() {
134                     handler.err(&format!("{:?} failed with: {}", cmd, o.status));
135                     handler.note(&format!("stdout ---\n{}",
136                                           str::from_utf8(&o.stdout).unwrap()));
137                     handler.note(&format!("stderr ---\n{}",
138                                           str::from_utf8(&o.stderr).unwrap()));
139                     handler.abort_if_errors();
140                 }
141                 o
142             },
143             Err(e) => {
144                 handler.err(&format!("could not exec `{}`: {}",
145                                      self.config.ar_prog, e));
146                 handler.abort_if_errors();
147                 panic!("rustc::back::archive::run() should not reach this point");
148             }
149         }
150     }
151
152     fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
153         match action {
154             Action::Remove(file) => {
155                 cmd.arg("d").arg(dst).arg(file);
156             }
157             Action::AddObjects(objs, update_symbols) => {
158                 cmd.arg(if update_symbols {"crs"} else {"crS"})
159                    .arg(dst)
160                    .args(objs);
161             }
162             Action::UpdateSymbols => {
163                 cmd.arg("s").arg(dst);
164             }
165         }
166     }
167 }
168
169 impl<'a> ArchiveBuilder<'a> {
170     fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
171         ArchiveBuilder {
172             archive: archive,
173             work_dir: TempDir::new("rsar").unwrap(),
174             members: vec![],
175             should_update_symbols: false,
176         }
177     }
178
179     /// Create a new static archive, ready for adding files.
180     pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
181         let archive = Archive::new(config);
182         ArchiveBuilder::new(archive)
183     }
184
185     /// Adds all of the contents of a native library to this archive. This will
186     /// search in the relevant locations for a library named `name`.
187     pub fn add_native_library(&mut self, name: &str) -> io::Result<()> {
188         let location = find_library(name,
189                                     &self.archive.config.slib_prefix,
190                                     &self.archive.config.slib_suffix,
191                                     &self.archive.config.lib_search_paths,
192                                     self.archive.config.handler);
193         self.add_archive(&location, name, |_| false)
194     }
195
196     /// Adds all of the contents of the rlib at the specified path to this
197     /// archive.
198     ///
199     /// This ignores adding the bytecode from the rlib, and if LTO is enabled
200     /// then the object file also isn't added.
201     pub fn add_rlib(&mut self, rlib: &Path, name: &str,
202                     lto: bool) -> io::Result<()> {
203         // Ignoring obj file starting with the crate name
204         // as simple comparison is not enough - there
205         // might be also an extra name suffix
206         let obj_start = format!("{}", name);
207         let obj_start = &obj_start[..];
208         // Ignoring all bytecode files, no matter of
209         // name
210         let bc_ext = ".bytecode.deflate";
211
212         self.add_archive(rlib, &name[..], |fname: &str| {
213             let skip_obj = lto && fname.starts_with(obj_start)
214                 && fname.ends_with(".o");
215             skip_obj || fname.ends_with(bc_ext) || fname == METADATA_FILENAME
216         })
217     }
218
219     /// Adds an arbitrary file to this archive
220     pub fn add_file(&mut self, file: &Path) -> io::Result<()> {
221         let filename = Path::new(file.file_name().unwrap());
222         let new_file = self.work_dir.path().join(&filename);
223         try!(fs::copy(file, &new_file));
224         self.members.push(filename.to_path_buf());
225         Ok(())
226     }
227
228     /// Indicate that the next call to `build` should updates all symbols in
229     /// the archive (run 'ar s' over it).
230     pub fn update_symbols(&mut self) {
231         self.should_update_symbols = true;
232     }
233
234     /// Combine the provided files, rlibs, and native libraries into a single
235     /// `Archive`.
236     pub fn build(self) -> Archive<'a> {
237         // Get an absolute path to the destination, so `ar` will work even
238         // though we run it from `self.work_dir`.
239         let mut objects = Vec::new();
240         let mut total_len = self.archive.config.dst.to_string_lossy().len();
241
242         if self.members.is_empty() {
243             if self.should_update_symbols {
244                 self.archive.run(Some(self.work_dir.path()),
245                                  Action::UpdateSymbols);
246             }
247             return self.archive;
248         }
249
250         // Don't allow the total size of `args` to grow beyond 32,000 bytes.
251         // Windows will raise an error if the argument string is longer than
252         // 32,768, and we leave a bit of extra space for the program name.
253         const ARG_LENGTH_LIMIT: usize = 32_000;
254
255         for member_name in &self.members {
256             let len = member_name.to_string_lossy().len();
257
258             // `len + 1` to account for the space that's inserted before each
259             // argument.  (Windows passes command-line arguments as a single
260             // string, not an array of strings.)
261             if total_len + len + 1 > ARG_LENGTH_LIMIT {
262                 // Add the archive members seen so far, without updating the
263                 // symbol table.
264                 self.archive.run(Some(self.work_dir.path()),
265                                  Action::AddObjects(&objects, false));
266
267                 objects.clear();
268                 total_len = self.archive.config.dst.to_string_lossy().len();
269             }
270
271             objects.push(member_name);
272             total_len += len + 1;
273         }
274
275         // Add the remaining archive members, and update the symbol table if
276         // necessary.
277         self.archive.run(Some(self.work_dir.path()),
278                          Action::AddObjects(&objects, self.should_update_symbols));
279
280         self.archive
281     }
282
283     fn add_archive<F>(&mut self, archive: &Path, name: &str,
284                       mut skip: F) -> io::Result<()>
285         where F: FnMut(&str) -> bool,
286     {
287         let archive = match ArchiveRO::open(archive) {
288             Some(ar) => ar,
289             None => return Err(io::Error::new(io::ErrorKind::Other,
290                                               "failed to open archive")),
291         };
292
293         // Next, we must rename all of the inputs to "guaranteed unique names".
294         // We write each file into `self.work_dir` under its new unique name.
295         // The reason for this renaming is that archives are keyed off the name
296         // of the files, so if two files have the same name they will override
297         // one another in the archive (bad).
298         //
299         // We skip any files explicitly desired for skipping, and we also skip
300         // all SYMDEF files as these are just magical placeholders which get
301         // re-created when we make a new archive anyway.
302         for file in archive.iter() {
303             let filename = match file.name() {
304                 Some(s) => s,
305                 None => continue,
306             };
307             if filename.contains(".SYMDEF") { continue }
308             if skip(filename) { continue }
309             let filename = Path::new(filename).file_name().unwrap()
310                                               .to_str().unwrap();
311
312             // Archives on unix systems typically do not have slashes in
313             // filenames as the `ar` utility generally only uses the last
314             // component of a path for the filename list in the archive. On
315             // Windows, however, archives assembled with `lib.exe` will preserve
316             // the full path to the file that was placed in the archive,
317             // including path separators.
318             //
319             // The code below is munging paths so it'll go wrong pretty quickly
320             // if there's some unexpected slashes in the filename, so here we
321             // just chop off everything but the filename component. Note that
322             // this can cause duplicate filenames, but that's also handled below
323             // as well.
324             let filename = Path::new(filename).file_name().unwrap()
325                                               .to_str().unwrap();
326
327             // An archive can contain files of the same name multiple times, so
328             // we need to be sure to not have them overwrite one another when we
329             // extract them. Consequently we need to find a truly unique file
330             // name for us!
331             let mut new_filename = String::new();
332             for n in 0.. {
333                 let n = if n == 0 {String::new()} else {format!("-{}", n)};
334                 new_filename = format!("r{}-{}-{}", n, name, filename);
335
336                 // LLDB (as mentioned in back::link) crashes on filenames of
337                 // exactly
338                 // 16 bytes in length. If we're including an object file with
339                 //    exactly 16-bytes of characters, give it some prefix so
340                 //    that it's not 16 bytes.
341                 new_filename = if new_filename.len() == 16 {
342                     format!("lldb-fix-{}", new_filename)
343                 } else {
344                     new_filename
345                 };
346
347                 let present = self.members.iter().filter_map(|p| {
348                     p.file_name().and_then(|f| f.to_str())
349                 }).any(|s| s == new_filename);
350                 if !present {
351                     break
352                 }
353             }
354             let dst = self.work_dir.path().join(&new_filename);
355             try!(try!(File::create(&dst)).write_all(file.data()));
356             self.members.push(PathBuf::from(new_filename));
357         }
358
359         Ok(())
360     }
361 }