1 //! A helper class for dealing with static archives
3 use std::ffi::{CString, CStr};
6 use std::path::{Path, PathBuf};
10 use back::bytecode::RLIB_BYTECODE_EXTENSION;
11 use rustc_codegen_ssa::back::archive::find_library;
13 use llvm::archive_ro::{ArchiveRO, Child};
14 use llvm::{self, ArchiveKind};
15 use metadata::METADATA_FILENAME;
16 use rustc::session::Session;
18 pub struct ArchiveConfig<'a> {
19 pub sess: &'a Session,
21 pub src: Option<PathBuf>,
22 pub lib_search_paths: Vec<PathBuf>,
25 /// Helper for adding many files to an archive.
26 #[must_use = "must call build() to finish building the archive"]
27 pub struct ArchiveBuilder<'a> {
28 config: ArchiveConfig<'a>,
29 removals: Vec<String>,
30 additions: Vec<Addition>,
31 should_update_symbols: bool,
32 src_archive: Option<Option<ArchiveRO>>,
38 name_in_archive: String,
42 skip: Box<dyn FnMut(&str) -> bool>,
46 fn is_relevant_child(c: &Child) -> bool {
48 Some(name) => !name.contains("SYMDEF"),
53 impl<'a> ArchiveBuilder<'a> {
54 /// Create a new static archive, ready for modifying the archive specified
56 pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
60 additions: Vec::new(),
61 should_update_symbols: false,
66 /// Removes a file from this archive
67 pub fn remove_file(&mut self, file: &str) {
68 self.removals.push(file.to_string());
71 /// Lists all files in an archive
72 pub fn src_files(&mut self) -> Vec<String> {
73 if self.src_archive().is_none() {
77 let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
80 .filter_map(|child| child.ok())
81 .filter(is_relevant_child)
82 .filter_map(|child| child.name())
83 .filter(|name| !self.removals.iter().any(|x| x == name))
84 .map(|name| name.to_owned())
88 fn src_archive(&mut self) -> Option<&ArchiveRO> {
89 if let Some(ref a) = self.src_archive {
92 let src = self.config.src.as_ref()?;
93 self.src_archive = Some(ArchiveRO::open(src).ok());
94 self.src_archive.as_ref().unwrap().as_ref()
97 /// Adds all of the contents of a native library to this archive. This will
98 /// search in the relevant locations for a library named `name`.
99 pub fn add_native_library(&mut self, name: &str) {
100 let location = find_library(name, &self.config.lib_search_paths,
102 self.add_archive(&location, |_| false).unwrap_or_else(|e| {
103 self.config.sess.fatal(&format!("failed to add native library {}: {}",
104 location.to_string_lossy(), e));
108 /// Adds all of the contents of the rlib at the specified path to this
111 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
112 /// then the object file also isn't added.
113 pub fn add_rlib(&mut self,
117 skip_objects: bool) -> io::Result<()> {
118 // Ignoring obj file starting with the crate name
119 // as simple comparison is not enough - there
120 // might be also an extra name suffix
121 let obj_start = name.to_owned();
123 self.add_archive(rlib, move |fname: &str| {
124 // Ignore bytecode/metadata files, no matter the name.
125 if fname.ends_with(RLIB_BYTECODE_EXTENSION) || fname == METADATA_FILENAME {
129 // Don't include Rust objects if LTO is enabled
130 if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") {
134 // Otherwise if this is *not* a rust object and we're skipping
135 // objects then skip this file
136 if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) {
140 // ok, don't skip this
145 fn add_archive<F>(&mut self, archive: &Path, skip: F)
147 where F: FnMut(&str) -> bool + 'static
149 let archive = match ArchiveRO::open(archive) {
151 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
153 self.additions.push(Addition::Archive {
155 skip: Box::new(skip),
160 /// Adds an arbitrary file to this archive
161 pub fn add_file(&mut self, file: &Path) {
162 let name = file.file_name().unwrap().to_str().unwrap();
163 self.additions.push(Addition::File {
164 path: file.to_path_buf(),
165 name_in_archive: name.to_owned(),
169 /// Indicate that the next call to `build` should update all symbols in
170 /// the archive (equivalent to running 'ar s' over it).
171 pub fn update_symbols(&mut self) {
172 self.should_update_symbols = true;
175 /// Combine the provided files, rlibs, and native libraries into a single
177 pub fn build(&mut self) {
178 let kind = self.llvm_archive_kind().unwrap_or_else(|kind|
179 self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", kind)));
181 if let Err(e) = self.build_with_llvm(kind) {
182 self.config.sess.fatal(&format!("failed to build archive: {}", e));
187 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
188 let kind = &*self.config.sess.target.target.options.archive_format;
189 kind.parse().map_err(|_| kind)
192 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
193 let removals = mem::replace(&mut self.removals, Vec::new());
194 let mut additions = mem::replace(&mut self.additions, Vec::new());
195 let mut strings = Vec::new();
196 let mut members = Vec::new();
198 let dst = CString::new(self.config.dst.to_str().unwrap())?;
199 let should_update_symbols = self.should_update_symbols;
202 if let Some(archive) = self.src_archive() {
203 for child in archive.iter() {
204 let child = child.map_err(string_to_io_error)?;
205 let child_name = match child.name() {
209 if removals.iter().any(|r| r == child_name) {
213 let name = CString::new(child_name)?;
214 members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
220 for addition in &mut additions {
222 Addition::File { path, name_in_archive } => {
223 let path = CString::new(path.to_str().unwrap())?;
224 let name = CString::new(name_in_archive.clone())?;
225 members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
231 Addition::Archive { archive, skip } => {
232 for child in archive.iter() {
233 let child = child.map_err(string_to_io_error)?;
234 if !is_relevant_child(&child) {
237 let child_name = child.name().unwrap();
238 if skip(child_name) {
242 // It appears that LLVM's archive writer is a little
243 // buggy if the name we pass down isn't just the
244 // filename component, so chop that off here and
247 // See LLVM bug 25877 for more info.
248 let child_name = Path::new(child_name)
249 .file_name().unwrap()
251 let name = CString::new(child_name)?;
252 let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
262 let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
263 members.len() as libc::size_t,
264 members.as_ptr() as *const &_,
265 should_update_symbols,
267 let ret = if r.into_result().is_err() {
268 let err = llvm::LLVMRustGetLastError();
269 let msg = if err.is_null() {
270 "failed to write archive".into()
272 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
274 Err(io::Error::new(io::ErrorKind::Other, msg))
278 for member in members {
279 llvm::LLVMRustArchiveMemberFree(member);
286 fn string_to_io_error(s: String) -> io::Error {
287 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))