]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
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 | ||
c1a9b12d | 13 | use std::ffi::{CString, CStr, OsString}; |
c1a9b12d SL |
14 | use std::io; |
15 | use std::mem; | |
16 | use std::path::{Path, PathBuf}; | |
e9174d1e | 17 | use std::ptr; |
c1a9b12d SL |
18 | use std::str; |
19 | ||
20 | use libc; | |
21 | use llvm::archive_ro::{ArchiveRO, Child}; | |
22 | use llvm::{self, ArchiveKind}; | |
7cac9316 | 23 | use metadata::METADATA_FILENAME; |
c1a9b12d | 24 | use rustc::session::Session; |
c1a9b12d SL |
25 | |
26 | pub struct ArchiveConfig<'a> { | |
27 | pub sess: &'a Session, | |
28 | pub dst: PathBuf, | |
29 | pub src: Option<PathBuf>, | |
30 | pub lib_search_paths: Vec<PathBuf>, | |
31 | pub ar_prog: String, | |
32 | pub command_path: OsString, | |
33 | } | |
34 | ||
35 | /// Helper for adding many files to an archive with a single invocation of | |
36 | /// `ar`. | |
37 | #[must_use = "must call build() to finish building the archive"] | |
38 | pub struct ArchiveBuilder<'a> { | |
39 | config: ArchiveConfig<'a>, | |
c1a9b12d SL |
40 | removals: Vec<String>, |
41 | additions: Vec<Addition>, | |
42 | should_update_symbols: bool, | |
43 | src_archive: Option<Option<ArchiveRO>>, | |
44 | } | |
45 | ||
46 | enum Addition { | |
47 | File { | |
48 | path: PathBuf, | |
49 | name_in_archive: String, | |
50 | }, | |
51 | Archive { | |
52 | archive: ArchiveRO, | |
c1a9b12d SL |
53 | skip: Box<FnMut(&str) -> bool>, |
54 | }, | |
55 | } | |
56 | ||
c1a9b12d SL |
57 | pub fn find_library(name: &str, search_paths: &[PathBuf], sess: &Session) |
58 | -> PathBuf { | |
59 | // On Windows, static libraries sometimes show up as libfoo.a and other | |
60 | // times show up as foo.lib | |
61 | let oslibname = format!("{}{}{}", | |
62 | sess.target.target.options.staticlib_prefix, | |
63 | name, | |
64 | sess.target.target.options.staticlib_suffix); | |
65 | let unixlibname = format!("lib{}.a", name); | |
66 | ||
67 | for path in search_paths { | |
68 | debug!("looking for {} inside {:?}", name, path); | |
cc61c64b | 69 | let test = path.join(&oslibname); |
c1a9b12d SL |
70 | if test.exists() { return test } |
71 | if oslibname != unixlibname { | |
cc61c64b | 72 | let test = path.join(&unixlibname); |
c1a9b12d SL |
73 | if test.exists() { return test } |
74 | } | |
75 | } | |
76 | sess.fatal(&format!("could not find native static library `{}`, \ | |
77 | perhaps an -L flag is missing?", name)); | |
78 | } | |
79 | ||
80 | fn is_relevant_child(c: &Child) -> bool { | |
81 | match c.name() { | |
82 | Some(name) => !name.contains("SYMDEF"), | |
83 | None => false, | |
84 | } | |
85 | } | |
86 | ||
87 | impl<'a> ArchiveBuilder<'a> { | |
88 | /// Create a new static archive, ready for modifying the archive specified | |
89 | /// by `config`. | |
90 | pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> { | |
91 | ArchiveBuilder { | |
92 | config: config, | |
c1a9b12d SL |
93 | removals: Vec::new(), |
94 | additions: Vec::new(), | |
95 | should_update_symbols: false, | |
96 | src_archive: None, | |
97 | } | |
98 | } | |
99 | ||
100 | /// Removes a file from this archive | |
101 | pub fn remove_file(&mut self, file: &str) { | |
102 | self.removals.push(file.to_string()); | |
103 | } | |
104 | ||
105 | /// Lists all files in an archive | |
106 | pub fn src_files(&mut self) -> Vec<String> { | |
107 | if self.src_archive().is_none() { | |
108 | return Vec::new() | |
109 | } | |
110 | let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap(); | |
111 | let ret = archive.iter() | |
7453a54e | 112 | .filter_map(|child| child.ok()) |
c1a9b12d SL |
113 | .filter(is_relevant_child) |
114 | .filter_map(|child| child.name()) | |
115 | .filter(|name| !self.removals.iter().any(|x| x == name)) | |
116 | .map(|name| name.to_string()) | |
117 | .collect(); | |
118 | return ret; | |
119 | } | |
120 | ||
121 | fn src_archive(&mut self) -> Option<&ArchiveRO> { | |
122 | if let Some(ref a) = self.src_archive { | |
123 | return a.as_ref() | |
124 | } | |
125 | let src = match self.config.src { | |
126 | Some(ref src) => src, | |
127 | None => return None, | |
128 | }; | |
129 | self.src_archive = Some(ArchiveRO::open(src)); | |
130 | self.src_archive.as_ref().unwrap().as_ref() | |
131 | } | |
132 | ||
133 | /// Adds all of the contents of a native library to this archive. This will | |
134 | /// search in the relevant locations for a library named `name`. | |
b039eaaf | 135 | pub fn add_native_library(&mut self, name: &str) { |
c1a9b12d SL |
136 | let location = find_library(name, &self.config.lib_search_paths, |
137 | self.config.sess); | |
3157f602 | 138 | self.add_archive(&location, |_| false).unwrap_or_else(|e| { |
b039eaaf SL |
139 | self.config.sess.fatal(&format!("failed to add native library {}: {}", |
140 | location.to_string_lossy(), e)); | |
141 | }); | |
c1a9b12d SL |
142 | } |
143 | ||
144 | /// Adds all of the contents of the rlib at the specified path to this | |
145 | /// archive. | |
146 | /// | |
147 | /// This ignores adding the bytecode from the rlib, and if LTO is enabled | |
148 | /// then the object file also isn't added. | |
476ff2be SL |
149 | pub fn add_rlib(&mut self, |
150 | rlib: &Path, | |
151 | name: &str, | |
152 | lto: bool, | |
153 | skip_objects: bool) -> io::Result<()> { | |
c1a9b12d SL |
154 | // Ignoring obj file starting with the crate name |
155 | // as simple comparison is not enough - there | |
156 | // might be also an extra name suffix | |
157 | let obj_start = format!("{}", name); | |
158 | ||
159 | // Ignoring all bytecode files, no matter of | |
160 | // name | |
161 | let bc_ext = ".bytecode.deflate"; | |
162 | ||
3157f602 | 163 | self.add_archive(rlib, move |fname: &str| { |
7cac9316 | 164 | if fname.ends_with(bc_ext) || fname == METADATA_FILENAME { |
476ff2be SL |
165 | return true |
166 | } | |
167 | ||
168 | // Don't include Rust objects if LTO is enabled | |
169 | if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") { | |
170 | return true | |
171 | } | |
172 | ||
173 | // Otherwise if this is *not* a rust object and we're skipping | |
174 | // objects then skip this file | |
175 | if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) { | |
176 | return true | |
177 | } | |
178 | ||
179 | // ok, don't skip this | |
180 | return false | |
c1a9b12d SL |
181 | }) |
182 | } | |
183 | ||
3157f602 | 184 | fn add_archive<F>(&mut self, archive: &Path, skip: F) |
c1a9b12d SL |
185 | -> io::Result<()> |
186 | where F: FnMut(&str) -> bool + 'static | |
187 | { | |
188 | let archive = match ArchiveRO::open(archive) { | |
189 | Some(ar) => ar, | |
190 | None => return Err(io::Error::new(io::ErrorKind::Other, | |
191 | "failed to open archive")), | |
192 | }; | |
193 | self.additions.push(Addition::Archive { | |
194 | archive: archive, | |
c1a9b12d SL |
195 | skip: Box::new(skip), |
196 | }); | |
197 | Ok(()) | |
198 | } | |
199 | ||
200 | /// Adds an arbitrary file to this archive | |
201 | pub fn add_file(&mut self, file: &Path) { | |
202 | let name = file.file_name().unwrap().to_str().unwrap(); | |
203 | self.additions.push(Addition::File { | |
204 | path: file.to_path_buf(), | |
205 | name_in_archive: name.to_string(), | |
206 | }); | |
207 | } | |
208 | ||
209 | /// Indicate that the next call to `build` should updates all symbols in | |
210 | /// the archive (run 'ar s' over it). | |
211 | pub fn update_symbols(&mut self) { | |
212 | self.should_update_symbols = true; | |
213 | } | |
214 | ||
215 | /// Combine the provided files, rlibs, and native libraries into a single | |
216 | /// `Archive`. | |
217 | pub fn build(&mut self) { | |
3157f602 XL |
218 | let kind = match self.llvm_archive_kind() { |
219 | Ok(kind) => kind, | |
220 | Err(kind) => { | |
221 | self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", | |
222 | kind)); | |
c1a9b12d | 223 | } |
3157f602 | 224 | }; |
c1a9b12d | 225 | |
3157f602 XL |
226 | if let Err(e) = self.build_with_llvm(kind) { |
227 | self.config.sess.fatal(&format!("failed to build archive: {}", e)); | |
c1a9b12d SL |
228 | } |
229 | ||
c1a9b12d SL |
230 | } |
231 | ||
3157f602 | 232 | fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> { |
476ff2be | 233 | let kind = &*self.config.sess.target.target.options.archive_format; |
3157f602 | 234 | kind.parse().map_err(|_| kind) |
c1a9b12d SL |
235 | } |
236 | ||
237 | fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> { | |
238 | let mut archives = Vec::new(); | |
239 | let mut strings = Vec::new(); | |
240 | let mut members = Vec::new(); | |
241 | let removals = mem::replace(&mut self.removals, Vec::new()); | |
242 | ||
243 | unsafe { | |
244 | if let Some(archive) = self.src_archive() { | |
245 | for child in archive.iter() { | |
54a0048b | 246 | let child = child.map_err(string_to_io_error)?; |
c1a9b12d SL |
247 | let child_name = match child.name() { |
248 | Some(s) => s, | |
249 | None => continue, | |
250 | }; | |
251 | if removals.iter().any(|r| r == child_name) { | |
252 | continue | |
253 | } | |
254 | ||
54a0048b | 255 | let name = CString::new(child_name)?; |
e9174d1e | 256 | members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
257 | name.as_ptr(), |
258 | child.raw())); | |
259 | strings.push(name); | |
260 | } | |
261 | } | |
262 | for addition in mem::replace(&mut self.additions, Vec::new()) { | |
263 | match addition { | |
264 | Addition::File { path, name_in_archive } => { | |
54a0048b SL |
265 | let path = CString::new(path.to_str().unwrap())?; |
266 | let name = CString::new(name_in_archive)?; | |
c1a9b12d SL |
267 | members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(), |
268 | name.as_ptr(), | |
e9174d1e | 269 | ptr::null_mut())); |
c1a9b12d SL |
270 | strings.push(path); |
271 | strings.push(name); | |
272 | } | |
3157f602 | 273 | Addition::Archive { archive, mut skip } => { |
7453a54e | 274 | for child in archive.iter() { |
54a0048b | 275 | let child = child.map_err(string_to_io_error)?; |
7453a54e SL |
276 | if !is_relevant_child(&child) { |
277 | continue | |
278 | } | |
c1a9b12d | 279 | let child_name = child.name().unwrap(); |
7453a54e SL |
280 | if skip(child_name) { |
281 | continue | |
282 | } | |
283 | ||
284 | // It appears that LLVM's archive writer is a little | |
285 | // buggy if the name we pass down isn't just the | |
286 | // filename component, so chop that off here and | |
287 | // pass it in. | |
288 | // | |
289 | // See LLVM bug 25877 for more info. | |
290 | let child_name = Path::new(child_name) | |
291 | .file_name().unwrap() | |
292 | .to_str().unwrap(); | |
54a0048b | 293 | let name = CString::new(child_name)?; |
e9174d1e | 294 | let m = llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
295 | name.as_ptr(), |
296 | child.raw()); | |
297 | members.push(m); | |
298 | strings.push(name); | |
299 | } | |
300 | archives.push(archive); | |
301 | } | |
302 | } | |
303 | } | |
304 | ||
305 | let dst = self.config.dst.to_str().unwrap().as_bytes(); | |
54a0048b | 306 | let dst = CString::new(dst)?; |
c1a9b12d SL |
307 | let r = llvm::LLVMRustWriteArchive(dst.as_ptr(), |
308 | members.len() as libc::size_t, | |
309 | members.as_ptr(), | |
310 | self.should_update_symbols, | |
311 | kind); | |
5bcae85e | 312 | let ret = if r.into_result().is_err() { |
c1a9b12d SL |
313 | let err = llvm::LLVMRustGetLastError(); |
314 | let msg = if err.is_null() { | |
315 | "failed to write archive".to_string() | |
316 | } else { | |
317 | String::from_utf8_lossy(CStr::from_ptr(err).to_bytes()) | |
318 | .into_owned() | |
319 | }; | |
320 | Err(io::Error::new(io::ErrorKind::Other, msg)) | |
321 | } else { | |
322 | Ok(()) | |
323 | }; | |
324 | for member in members { | |
325 | llvm::LLVMRustArchiveMemberFree(member); | |
326 | } | |
327 | return ret | |
328 | } | |
329 | } | |
330 | } | |
7453a54e SL |
331 | |
332 | fn string_to_io_error(s: String) -> io::Error { | |
333 | io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) | |
334 | } |