]>
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 | ||
ea8adc8c | 20 | use back::bytecode::RLIB_BYTECODE_EXTENSION; |
c1a9b12d SL |
21 | use libc; |
22 | use llvm::archive_ro::{ArchiveRO, Child}; | |
23 | use llvm::{self, ArchiveKind}; | |
7cac9316 | 24 | use metadata::METADATA_FILENAME; |
c1a9b12d | 25 | use rustc::session::Session; |
c1a9b12d SL |
26 | |
27 | pub struct ArchiveConfig<'a> { | |
28 | pub sess: &'a Session, | |
29 | pub dst: PathBuf, | |
30 | pub src: Option<PathBuf>, | |
31 | pub lib_search_paths: Vec<PathBuf>, | |
c1a9b12d SL |
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); | |
cc61c64b | 68 | let test = path.join(&oslibname); |
c1a9b12d SL |
69 | if test.exists() { return test } |
70 | if oslibname != unixlibname { | |
cc61c64b | 71 | let test = path.join(&unixlibname); |
c1a9b12d SL |
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 { | |
3b2f2976 | 91 | 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 | }; | |
3b2f2976 | 128 | self.src_archive = Some(ArchiveRO::open(src).ok()); |
c1a9b12d SL |
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 | ||
3157f602 | 158 | self.add_archive(rlib, move |fname: &str| { |
ea8adc8c XL |
159 | // Ignore bytecode/metadata files, no matter the name. |
160 | if fname.ends_with(RLIB_BYTECODE_EXTENSION) || fname == METADATA_FILENAME { | |
476ff2be SL |
161 | return true |
162 | } | |
163 | ||
164 | // Don't include Rust objects if LTO is enabled | |
165 | if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") { | |
166 | return true | |
167 | } | |
168 | ||
169 | // Otherwise if this is *not* a rust object and we're skipping | |
170 | // objects then skip this file | |
171 | if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) { | |
172 | return true | |
173 | } | |
174 | ||
175 | // ok, don't skip this | |
176 | return false | |
c1a9b12d SL |
177 | }) |
178 | } | |
179 | ||
3157f602 | 180 | fn add_archive<F>(&mut self, archive: &Path, skip: F) |
c1a9b12d SL |
181 | -> io::Result<()> |
182 | where F: FnMut(&str) -> bool + 'static | |
183 | { | |
184 | let archive = match ArchiveRO::open(archive) { | |
3b2f2976 XL |
185 | Ok(ar) => ar, |
186 | Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), | |
c1a9b12d SL |
187 | }; |
188 | self.additions.push(Addition::Archive { | |
3b2f2976 | 189 | archive, |
c1a9b12d SL |
190 | skip: Box::new(skip), |
191 | }); | |
192 | Ok(()) | |
193 | } | |
194 | ||
195 | /// Adds an arbitrary file to this archive | |
196 | pub fn add_file(&mut self, file: &Path) { | |
197 | let name = file.file_name().unwrap().to_str().unwrap(); | |
198 | self.additions.push(Addition::File { | |
199 | path: file.to_path_buf(), | |
200 | name_in_archive: name.to_string(), | |
201 | }); | |
202 | } | |
203 | ||
204 | /// Indicate that the next call to `build` should updates all symbols in | |
205 | /// the archive (run 'ar s' over it). | |
206 | pub fn update_symbols(&mut self) { | |
207 | self.should_update_symbols = true; | |
208 | } | |
209 | ||
210 | /// Combine the provided files, rlibs, and native libraries into a single | |
211 | /// `Archive`. | |
212 | pub fn build(&mut self) { | |
3157f602 XL |
213 | let kind = match self.llvm_archive_kind() { |
214 | Ok(kind) => kind, | |
215 | Err(kind) => { | |
216 | self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", | |
217 | kind)); | |
c1a9b12d | 218 | } |
3157f602 | 219 | }; |
c1a9b12d | 220 | |
3157f602 XL |
221 | if let Err(e) = self.build_with_llvm(kind) { |
222 | self.config.sess.fatal(&format!("failed to build archive: {}", e)); | |
c1a9b12d SL |
223 | } |
224 | ||
c1a9b12d SL |
225 | } |
226 | ||
3157f602 | 227 | fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> { |
476ff2be | 228 | let kind = &*self.config.sess.target.target.options.archive_format; |
3157f602 | 229 | kind.parse().map_err(|_| kind) |
c1a9b12d SL |
230 | } |
231 | ||
232 | fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> { | |
233 | let mut archives = Vec::new(); | |
234 | let mut strings = Vec::new(); | |
235 | let mut members = Vec::new(); | |
236 | let removals = mem::replace(&mut self.removals, Vec::new()); | |
237 | ||
238 | unsafe { | |
239 | if let Some(archive) = self.src_archive() { | |
240 | for child in archive.iter() { | |
54a0048b | 241 | let child = child.map_err(string_to_io_error)?; |
c1a9b12d SL |
242 | let child_name = match child.name() { |
243 | Some(s) => s, | |
244 | None => continue, | |
245 | }; | |
246 | if removals.iter().any(|r| r == child_name) { | |
247 | continue | |
248 | } | |
249 | ||
54a0048b | 250 | let name = CString::new(child_name)?; |
e9174d1e | 251 | members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
252 | name.as_ptr(), |
253 | child.raw())); | |
254 | strings.push(name); | |
255 | } | |
256 | } | |
257 | for addition in mem::replace(&mut self.additions, Vec::new()) { | |
258 | match addition { | |
259 | Addition::File { path, name_in_archive } => { | |
54a0048b SL |
260 | let path = CString::new(path.to_str().unwrap())?; |
261 | let name = CString::new(name_in_archive)?; | |
c1a9b12d SL |
262 | members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(), |
263 | name.as_ptr(), | |
e9174d1e | 264 | ptr::null_mut())); |
c1a9b12d SL |
265 | strings.push(path); |
266 | strings.push(name); | |
267 | } | |
3157f602 | 268 | Addition::Archive { archive, mut skip } => { |
7453a54e | 269 | for child in archive.iter() { |
54a0048b | 270 | let child = child.map_err(string_to_io_error)?; |
7453a54e SL |
271 | if !is_relevant_child(&child) { |
272 | continue | |
273 | } | |
c1a9b12d | 274 | let child_name = child.name().unwrap(); |
7453a54e SL |
275 | if skip(child_name) { |
276 | continue | |
277 | } | |
278 | ||
279 | // It appears that LLVM's archive writer is a little | |
280 | // buggy if the name we pass down isn't just the | |
281 | // filename component, so chop that off here and | |
282 | // pass it in. | |
283 | // | |
284 | // See LLVM bug 25877 for more info. | |
285 | let child_name = Path::new(child_name) | |
286 | .file_name().unwrap() | |
287 | .to_str().unwrap(); | |
54a0048b | 288 | let name = CString::new(child_name)?; |
e9174d1e | 289 | let m = llvm::LLVMRustArchiveMemberNew(ptr::null(), |
c1a9b12d SL |
290 | name.as_ptr(), |
291 | child.raw()); | |
292 | members.push(m); | |
293 | strings.push(name); | |
294 | } | |
295 | archives.push(archive); | |
296 | } | |
297 | } | |
298 | } | |
299 | ||
300 | let dst = self.config.dst.to_str().unwrap().as_bytes(); | |
54a0048b | 301 | let dst = CString::new(dst)?; |
c1a9b12d SL |
302 | let r = llvm::LLVMRustWriteArchive(dst.as_ptr(), |
303 | members.len() as libc::size_t, | |
304 | members.as_ptr(), | |
305 | self.should_update_symbols, | |
306 | kind); | |
5bcae85e | 307 | let ret = if r.into_result().is_err() { |
c1a9b12d SL |
308 | let err = llvm::LLVMRustGetLastError(); |
309 | let msg = if err.is_null() { | |
310 | "failed to write archive".to_string() | |
311 | } else { | |
312 | String::from_utf8_lossy(CStr::from_ptr(err).to_bytes()) | |
313 | .into_owned() | |
314 | }; | |
315 | Err(io::Error::new(io::ErrorKind::Other, msg)) | |
316 | } else { | |
317 | Ok(()) | |
318 | }; | |
319 | for member in members { | |
320 | llvm::LLVMRustArchiveMemberFree(member); | |
321 | } | |
322 | return ret | |
323 | } | |
324 | } | |
325 | } | |
7453a54e SL |
326 | |
327 | fn string_to_io_error(s: String) -> io::Error { | |
328 | io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) | |
329 | } |