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