]> git.lizzy.rs Git - rust.git/blob - src/librustc/back/archive.rs
e0c570664fe5c9a38885962dee7fec92e7f1e709
[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 back::link::{get_ar_prog};
14 use driver::session::Session;
15 use metadata::filesearch;
16 use lib::llvm::{ArchiveRef, llvm};
17
18 use std::cast;
19 use std::io::fs;
20 use std::io;
21 use std::libc;
22 use std::os;
23 use std::io::process::{ProcessConfig, Process, ProcessOutput};
24 use std::str;
25 use std::raw;
26 use extra::tempfile::TempDir;
27 use syntax::abi;
28
29 pub static METADATA_FILENAME: &'static str = "rust.metadata.bin";
30
31 pub struct Archive {
32     priv sess: Session,
33     priv dst: Path,
34 }
35
36 pub struct ArchiveRO {
37     priv ptr: ArchiveRef,
38 }
39
40 fn run_ar(sess: Session, args: &str, cwd: Option<&Path>,
41         paths: &[&Path]) -> ProcessOutput {
42     let ar = get_ar_prog(sess);
43
44     let mut args = ~[args.to_owned()];
45     let mut paths = paths.iter().map(|p| p.as_str().unwrap().to_owned());
46     args.extend(&mut paths);
47     debug!("{} {}", ar, args.connect(" "));
48     match cwd {
49         Some(p) => { debug!("inside {}", p.display()); }
50         None => {}
51     }
52     match Process::configure(ProcessConfig {
53         program: ar.as_slice(),
54         args: args.as_slice(),
55         cwd: cwd.map(|a| &*a),
56         .. ProcessConfig::new()
57     }) {
58         Ok(mut prog) => {
59             let o = prog.wait_with_output();
60             if !o.status.success() {
61                 sess.err(format!("{} {} failed with: {}", ar, args.connect(" "),
62                                  o.status));
63                 sess.note(format!("stdout ---\n{}", str::from_utf8(o.output).unwrap()));
64                 sess.note(format!("stderr ---\n{}", str::from_utf8(o.error).unwrap()));
65                 sess.abort_if_errors();
66             }
67             o
68         },
69         Err(e) => {
70             sess.err(format!("could not exec `{}`: {}", ar, e));
71             sess.abort_if_errors();
72             fail!("rustc::back::archive::run_ar() should not reach this point");
73         }
74     }
75 }
76
77 impl Archive {
78     /// Initializes a new static archive with the given object file
79     pub fn create<'a>(sess: Session, dst: &'a Path,
80                       initial_object: &'a Path) -> Archive {
81         run_ar(sess, "crus", None, [dst, initial_object]);
82         Archive { sess: sess, dst: dst.clone() }
83     }
84
85     /// Opens an existing static archive
86     pub fn open(sess: Session, dst: Path) -> Archive {
87         assert!(dst.exists());
88         Archive { sess: sess, dst: dst }
89     }
90
91     /// Read a file in the archive
92     pub fn read(&self, file: &str) -> ~[u8] {
93         // Apparently if "ar p" is used on windows, it generates a corrupt file
94         // which has bad headers and LLVM will immediately choke on it
95         if cfg!(windows) && cfg!(windows) { // FIXME(#10734) double-and
96             let loc = TempDir::new("rsar").unwrap();
97             let archive = os::make_absolute(&self.dst);
98             run_ar(self.sess, "x", Some(loc.path()), [&archive,
99                                                       &Path::new(file)]);
100             fs::File::open(&loc.path().join(file)).read_to_end().unwrap()
101         } else {
102             run_ar(self.sess, "p", None, [&self.dst, &Path::new(file)]).output
103         }
104     }
105
106     /// Adds all of the contents of a native library to this archive. This will
107     /// search in the relevant locations for a library named `name`.
108     pub fn add_native_library(&mut self, name: &str) -> io::IoResult<()> {
109         let location = self.find_library(name);
110         self.add_archive(&location, name, [])
111     }
112
113     /// Adds all of the contents of the rlib at the specified path to this
114     /// archive.
115     ///
116     /// This ignores adding the bytecode from the rlib, and if LTO is enabled
117     /// then the object file also isn't added.
118     pub fn add_rlib(&mut self, rlib: &Path, name: &str,
119                     lto: bool) -> io::IoResult<()> {
120         let object = format!("{}.o", name);
121         let bytecode = format!("{}.bc", name);
122         let mut ignore = ~[METADATA_FILENAME, bytecode.as_slice()];
123         if lto {
124             ignore.push(object.as_slice());
125         }
126         self.add_archive(rlib, name, ignore)
127     }
128
129     /// Adds an arbitrary file to this archive
130     pub fn add_file(&mut self, file: &Path, has_symbols: bool) {
131         let cmd = if has_symbols {"r"} else {"rS"};
132         run_ar(self.sess, cmd, None, [&self.dst, file]);
133     }
134
135     /// Removes a file from this archive
136     pub fn remove_file(&mut self, file: &str) {
137         run_ar(self.sess, "d", None, [&self.dst, &Path::new(file)]);
138     }
139
140     /// Updates all symbols in the archive (runs 'ar s' over it)
141     pub fn update_symbols(&mut self) {
142         run_ar(self.sess, "s", None, [&self.dst]);
143     }
144
145     /// Lists all files in an archive
146     pub fn files(&self) -> ~[~str] {
147         let output = run_ar(self.sess, "t", None, [&self.dst]);
148         let output = str::from_utf8(output.output).unwrap();
149         // use lines_any because windows delimits output with `\r\n` instead of
150         // just `\n`
151         output.lines_any().map(|s| s.to_owned()).collect()
152     }
153
154     fn add_archive(&mut self, archive: &Path, name: &str,
155                    skip: &[&str]) -> io::IoResult<()> {
156         let loc = TempDir::new("rsar").unwrap();
157
158         // First, extract the contents of the archive to a temporary directory
159         let archive = os::make_absolute(archive);
160         run_ar(self.sess, "x", Some(loc.path()), [&archive]);
161
162         // Next, we must rename all of the inputs to "guaranteed unique names".
163         // The reason for this is that archives are keyed off the name of the
164         // files, so if two files have the same name they will override one
165         // another in the archive (bad).
166         //
167         // We skip any files explicitly desired for skipping, and we also skip
168         // all SYMDEF files as these are just magical placeholders which get
169         // re-created when we make a new archive anyway.
170         let files = try!(fs::readdir(loc.path()));
171         let mut inputs = ~[];
172         for file in files.iter() {
173             let filename = file.filename_str().unwrap();
174             if skip.iter().any(|s| *s == filename) { continue }
175             if filename.contains(".SYMDEF") { continue }
176
177             let filename = format!("r-{}-{}", name, filename);
178             let new_filename = file.with_filename(filename);
179             try!(fs::rename(file, &new_filename));
180             inputs.push(new_filename);
181         }
182         if inputs.len() == 0 { return Ok(()) }
183
184         // Finally, add all the renamed files to this archive
185         let mut args = ~[&self.dst];
186         args.extend(&mut inputs.iter());
187         run_ar(self.sess, "r", None, args.as_slice());
188         Ok(())
189     }
190
191     fn find_library(&self, name: &str) -> Path {
192         let (osprefix, osext) = match self.sess.targ_cfg.os {
193             abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
194         };
195         // On Windows, static libraries sometimes show up as libfoo.a and other
196         // times show up as foo.lib
197         let oslibname = format!("{}{}.{}", osprefix, name, osext);
198         let unixlibname = format!("lib{}.a", name);
199
200         let mut rustpath = filesearch::rust_path();
201         rustpath.push(self.sess.filesearch.get_target_lib_path());
202         let addl_lib_search_paths = self.sess
203                                         .opts
204                                         .addl_lib_search_paths
205                                         .borrow();
206         let path = addl_lib_search_paths.get().iter();
207         for path in path.chain(rustpath.iter()) {
208             debug!("looking for {} inside {}", name, path.display());
209             let test = path.join(oslibname.as_slice());
210             if test.exists() { return test }
211             if oslibname != unixlibname {
212                 let test = path.join(unixlibname.as_slice());
213                 if test.exists() { return test }
214             }
215         }
216         self.sess.fatal(format!("could not find native static library `{}`, \
217                                  perhaps an -L flag is missing?", name));
218     }
219 }
220
221 impl ArchiveRO {
222     /// Opens a static archive for read-only purposes. This is more optimized
223     /// than the `open` method because it uses LLVM's internal `Archive` class
224     /// rather than shelling out to `ar` for everything.
225     ///
226     /// If this archive is used with a mutable method, then an error will be
227     /// raised.
228     pub fn open(dst: &Path) -> Option<ArchiveRO> {
229         unsafe {
230             let ar = dst.with_c_str(|dst| {
231                 llvm::LLVMRustOpenArchive(dst)
232             });
233             if ar.is_null() {
234                 None
235             } else {
236                 Some(ArchiveRO { ptr: ar })
237             }
238         }
239     }
240
241     /// Reads a file in the archive
242     pub fn read<'a>(&'a self, file: &str) -> Option<&'a [u8]> {
243         unsafe {
244             let mut size = 0 as libc::size_t;
245             let ptr = file.with_c_str(|file| {
246                 llvm::LLVMRustArchiveReadSection(self.ptr, file, &mut size)
247             });
248             if ptr.is_null() {
249                 None
250             } else {
251                 Some(cast::transmute(raw::Slice {
252                     data: ptr,
253                     len: size as uint,
254                 }))
255             }
256         }
257     }
258 }
259
260 impl Drop for ArchiveRO {
261     fn drop(&mut self) {
262         unsafe {
263             llvm::LLVMRustDestroyArchive(self.ptr);
264         }
265     }
266 }