]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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 | ||
85aaf69f | 13 | use std::env; |
62682a34 | 14 | use std::ffi::OsString; |
d9579d0f | 15 | use std::fs::{self, File}; |
c34b1796 AL |
16 | use std::io::prelude::*; |
17 | use std::io; | |
18 | use std::path::{Path, PathBuf}; | |
19 | use std::process::{Command, Output, Stdio}; | |
1a4d82fc JJ |
20 | use std::str; |
21 | use syntax::diagnostic::Handler as ErrorHandler; | |
d9579d0f | 22 | use rustc_llvm::archive_ro::ArchiveRO; |
1a4d82fc | 23 | |
c34b1796 AL |
24 | use tempdir::TempDir; |
25 | ||
26 | pub const METADATA_FILENAME: &'static str = "rust.metadata.bin"; | |
1a4d82fc JJ |
27 | |
28 | pub struct ArchiveConfig<'a> { | |
29 | pub handler: &'a ErrorHandler, | |
c34b1796 AL |
30 | pub dst: PathBuf, |
31 | pub lib_search_paths: Vec<PathBuf>, | |
1a4d82fc JJ |
32 | pub slib_prefix: String, |
33 | pub slib_suffix: String, | |
62682a34 SL |
34 | pub ar_prog: String, |
35 | pub command_path: OsString, | |
1a4d82fc JJ |
36 | } |
37 | ||
38 | pub struct Archive<'a> { | |
62682a34 | 39 | config: ArchiveConfig<'a>, |
1a4d82fc JJ |
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. | |
c34b1796 | 49 | members: Vec<PathBuf>, |
1a4d82fc JJ |
50 | should_update_symbols: bool, |
51 | } | |
52 | ||
62682a34 SL |
53 | enum Action<'a> { |
54 | Remove(&'a Path), | |
55 | AddObjects(&'a [&'a PathBuf], bool), | |
56 | UpdateSymbols, | |
1a4d82fc JJ |
57 | } |
58 | ||
59 | pub fn find_library(name: &str, osprefix: &str, ossuffix: &str, | |
c34b1796 AL |
60 | search_paths: &[PathBuf], |
61 | handler: &ErrorHandler) -> PathBuf { | |
1a4d82fc JJ |
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 | ||
85aaf69f | 67 | for path in search_paths { |
c34b1796 | 68 | debug!("looking for {} inside {:?}", name, path); |
85aaf69f | 69 | let test = path.join(&oslibname[..]); |
1a4d82fc JJ |
70 | if test.exists() { return test } |
71 | if oslibname != unixlibname { | |
85aaf69f | 72 | let test = path.join(&unixlibname[..]); |
1a4d82fc JJ |
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?", | |
c34b1796 | 78 | name)); |
1a4d82fc JJ |
79 | } |
80 | ||
81 | impl<'a> Archive<'a> { | |
82 | fn new(config: ArchiveConfig<'a>) -> Archive<'a> { | |
62682a34 | 83 | Archive { config: config } |
1a4d82fc JJ |
84 | } |
85 | ||
86 | /// Opens an existing static archive | |
87 | pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> { | |
88 | let archive = Archive::new(config); | |
62682a34 | 89 | assert!(archive.config.dst.exists()); |
1a4d82fc JJ |
90 | archive |
91 | } | |
92 | ||
93 | /// Removes a file from this archive | |
94 | pub fn remove_file(&mut self, file: &str) { | |
62682a34 | 95 | self.run(None, Action::Remove(Path::new(file))); |
1a4d82fc JJ |
96 | } |
97 | ||
98 | /// Lists all files in an archive | |
99 | pub fn files(&self) -> Vec<String> { | |
62682a34 SL |
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; | |
1a4d82fc JJ |
108 | } |
109 | ||
110 | /// Creates an `ArchiveBuilder` for adding files to this archive. | |
111 | pub fn extend(self) -> ArchiveBuilder<'a> { | |
112 | ArchiveBuilder::new(self) | |
113 | } | |
62682a34 SL |
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 | } | |
1a4d82fc JJ |
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`. | |
c34b1796 | 187 | pub fn add_native_library(&mut self, name: &str) -> io::Result<()> { |
1a4d82fc | 188 | let location = find_library(name, |
62682a34 SL |
189 | &self.archive.config.slib_prefix, |
190 | &self.archive.config.slib_suffix, | |
191 | &self.archive.config.lib_search_paths, | |
192 | self.archive.config.handler); | |
1a4d82fc JJ |
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, | |
c34b1796 | 202 | lto: bool) -> io::Result<()> { |
1a4d82fc JJ |
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); | |
85aaf69f | 207 | let obj_start = &obj_start[..]; |
1a4d82fc JJ |
208 | // Ignoring all bytecode files, no matter of |
209 | // name | |
210 | let bc_ext = ".bytecode.deflate"; | |
211 | ||
85aaf69f | 212 | self.add_archive(rlib, &name[..], |fname: &str| { |
1a4d82fc JJ |
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 | |
c34b1796 AL |
220 | pub fn add_file(&mut self, file: &Path) -> io::Result<()> { |
221 | let filename = Path::new(file.file_name().unwrap()); | |
1a4d82fc JJ |
222 | let new_file = self.work_dir.path().join(&filename); |
223 | try!(fs::copy(file, &new_file)); | |
c34b1796 | 224 | self.members.push(filename.to_path_buf()); |
1a4d82fc JJ |
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`. | |
62682a34 SL |
239 | let mut objects = Vec::new(); |
240 | let mut total_len = self.archive.config.dst.to_string_lossy().len(); | |
1a4d82fc JJ |
241 | |
242 | if self.members.is_empty() { | |
1a4d82fc | 243 | if self.should_update_symbols { |
62682a34 SL |
244 | self.archive.run(Some(self.work_dir.path()), |
245 | Action::UpdateSymbols); | |
1a4d82fc JJ |
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. | |
c34b1796 | 253 | const ARG_LENGTH_LIMIT: usize = 32_000; |
1a4d82fc | 254 | |
85aaf69f | 255 | for member_name in &self.members { |
c34b1796 | 256 | let len = member_name.to_string_lossy().len(); |
1a4d82fc JJ |
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 | |
62682a34 SL |
263 | // symbol table. |
264 | self.archive.run(Some(self.work_dir.path()), | |
265 | Action::AddObjects(&objects, false)); | |
1a4d82fc | 266 | |
62682a34 SL |
267 | objects.clear(); |
268 | total_len = self.archive.config.dst.to_string_lossy().len(); | |
1a4d82fc JJ |
269 | } |
270 | ||
62682a34 | 271 | objects.push(member_name); |
1a4d82fc JJ |
272 | total_len += len + 1; |
273 | } | |
274 | ||
275 | // Add the remaining archive members, and update the symbol table if | |
276 | // necessary. | |
62682a34 SL |
277 | self.archive.run(Some(self.work_dir.path()), |
278 | Action::AddObjects(&objects, self.should_update_symbols)); | |
1a4d82fc JJ |
279 | |
280 | self.archive | |
281 | } | |
282 | ||
85aaf69f | 283 | fn add_archive<F>(&mut self, archive: &Path, name: &str, |
c34b1796 | 284 | mut skip: F) -> io::Result<()> |
85aaf69f | 285 | where F: FnMut(&str) -> bool, |
1a4d82fc | 286 | { |
d9579d0f AL |
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 | }; | |
1a4d82fc JJ |
292 | |
293 | // Next, we must rename all of the inputs to "guaranteed unique names". | |
d9579d0f | 294 | // We write each file into `self.work_dir` under its new unique name. |
1a4d82fc JJ |
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. | |
d9579d0f AL |
302 | for file in archive.iter() { |
303 | let filename = match file.name() { | |
304 | Some(s) => s, | |
305 | None => continue, | |
306 | }; | |
1a4d82fc | 307 | if filename.contains(".SYMDEF") { continue } |
d9579d0f | 308 | if skip(filename) { continue } |
62682a34 SL |
309 | let filename = Path::new(filename).file_name().unwrap() |
310 | .to_str().unwrap(); | |
1a4d82fc | 311 | |
d9579d0f AL |
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)); | |
1a4d82fc | 357 | } |
d9579d0f | 358 | |
1a4d82fc JJ |
359 | Ok(()) |
360 | } | |
361 | } |