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