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