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