1 //! A helper class for dealing with static archives
3 use std::ffi::{CString, CStr};
6 use std::path::{Path, PathBuf};
10 use crate::back::bytecode::RLIB_BYTECODE_EXTENSION;
11 use crate::llvm::archive_ro::{ArchiveRO, Child};
12 use crate::llvm::{self, ArchiveKind};
13 use crate::metadata::METADATA_FILENAME;
14 use rustc_codegen_ssa::back::archive::find_library;
15 use rustc::session::Session;
17 pub struct ArchiveConfig<'a> {
18 pub sess: &'a Session,
20 pub src: Option<PathBuf>,
21 pub lib_search_paths: Vec<PathBuf>,
24 /// Helper for adding many files to an archive.
25 #[must_use = "must call build() to finish building the archive"]
26 pub struct ArchiveBuilder<'a> {
27 config: ArchiveConfig<'a>,
28 removals: Vec<String>,
29 additions: Vec<Addition>,
30 should_update_symbols: bool,
31 src_archive: Option<Option<ArchiveRO>>,
37 name_in_archive: String,
41 skip: Box<dyn FnMut(&str) -> bool>,
45 fn is_relevant_child(c: &Child<'_>) -> bool {
47 Some(name) => !name.contains("SYMDEF"),
52 impl<'a> ArchiveBuilder<'a> {
53 /// Creates a new static archive, ready for modifying the archive specified
55 pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
59 additions: Vec::new(),
60 should_update_symbols: false,
65 /// Removes a file from this archive
66 pub fn remove_file(&mut self, file: &str) {
67 self.removals.push(file.to_string());
70 /// Lists all files in an archive
71 pub fn src_files(&mut self) -> Vec<String> {
72 if self.src_archive().is_none() {
76 let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
79 .filter_map(|child| child.ok())
80 .filter(is_relevant_child)
81 .filter_map(|child| child.name())
82 .filter(|name| !self.removals.iter().any(|x| x == name))
83 .map(|name| name.to_owned())
87 fn src_archive(&mut self) -> Option<&ArchiveRO> {
88 if let Some(ref a) = self.src_archive {
91 let src = self.config.src.as_ref()?;
92 self.src_archive = Some(ArchiveRO::open(src).ok());
93 self.src_archive.as_ref().unwrap().as_ref()
96 /// Adds all of the contents of a native library to this archive. This will
97 /// search in the relevant locations for a library named `name`.
98 pub fn add_native_library(&mut self, name: &str) {
99 let location = find_library(name, &self.config.lib_search_paths,
101 self.add_archive(&location, |_| false).unwrap_or_else(|e| {
102 self.config.sess.fatal(&format!("failed to add native library {}: {}",
103 location.to_string_lossy(), e));
107 /// Adds all of the contents of the rlib at the specified path to this
110 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
111 /// then the object file also isn't added.
112 pub fn add_rlib(&mut self,
116 skip_objects: bool) -> io::Result<()> {
117 // Ignoring obj file starting with the crate name
118 // as simple comparison is not enough - there
119 // might be also an extra name suffix
120 let obj_start = name.to_owned();
122 self.add_archive(rlib, move |fname: &str| {
123 // Ignore bytecode/metadata files, no matter the name.
124 if fname.ends_with(RLIB_BYTECODE_EXTENSION) || fname == METADATA_FILENAME {
128 // Don't include Rust objects if LTO is enabled
129 if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") {
133 // Otherwise if this is *not* a rust object and we're skipping
134 // objects then skip this file
135 if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) {
139 // ok, don't skip this
144 fn add_archive<F>(&mut self, archive: &Path, skip: F)
146 where F: FnMut(&str) -> bool + 'static
148 let archive = match ArchiveRO::open(archive) {
150 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
152 self.additions.push(Addition::Archive {
154 skip: Box::new(skip),
159 /// Adds an arbitrary file to this archive
160 pub fn add_file(&mut self, file: &Path) {
161 let name = file.file_name().unwrap().to_str().unwrap();
162 self.additions.push(Addition::File {
163 path: file.to_path_buf(),
164 name_in_archive: name.to_owned(),
168 /// Indicate that the next call to `build` should update all symbols in
169 /// the archive (equivalent to running 'ar s' over it).
170 pub fn update_symbols(&mut self) {
171 self.should_update_symbols = true;
174 /// Combine the provided files, rlibs, and native libraries into a single
176 pub fn build(&mut self) {
177 let kind = self.llvm_archive_kind().unwrap_or_else(|kind|
178 self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", kind)));
180 if let Err(e) = self.build_with_llvm(kind) {
181 self.config.sess.fatal(&format!("failed to build archive: {}", e));
186 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
187 let kind = &*self.config.sess.target.target.options.archive_format;
188 kind.parse().map_err(|_| kind)
191 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
192 let removals = mem::replace(&mut self.removals, Vec::new());
193 let mut additions = mem::replace(&mut self.additions, Vec::new());
194 let mut strings = Vec::new();
195 let mut members = Vec::new();
197 let dst = CString::new(self.config.dst.to_str().unwrap())?;
198 let should_update_symbols = self.should_update_symbols;
201 if let Some(archive) = self.src_archive() {
202 for child in archive.iter() {
203 let child = child.map_err(string_to_io_error)?;
204 let child_name = match child.name() {
208 if removals.iter().any(|r| r == child_name) {
212 let name = CString::new(child_name)?;
213 members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
219 for addition in &mut additions {
221 Addition::File { path, name_in_archive } => {
222 let path = CString::new(path.to_str().unwrap())?;
223 let name = CString::new(name_in_archive.clone())?;
224 members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
230 Addition::Archive { archive, skip } => {
231 for child in archive.iter() {
232 let child = child.map_err(string_to_io_error)?;
233 if !is_relevant_child(&child) {
236 let child_name = child.name().unwrap();
237 if skip(child_name) {
241 // It appears that LLVM's archive writer is a little
242 // buggy if the name we pass down isn't just the
243 // filename component, so chop that off here and
246 // See LLVM bug 25877 for more info.
247 let child_name = Path::new(child_name)
248 .file_name().unwrap()
250 let name = CString::new(child_name)?;
251 let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
261 let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
262 members.len() as libc::size_t,
263 members.as_ptr() as *const &_,
264 should_update_symbols,
266 let ret = if r.into_result().is_err() {
267 let err = llvm::LLVMRustGetLastError();
268 let msg = if err.is_null() {
269 "failed to write archive".into()
271 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
273 Err(io::Error::new(io::ErrorKind::Other, msg))
277 for member in members {
278 llvm::LLVMRustArchiveMemberFree(member);
285 fn string_to_io_error(s: String) -> io::Error {
286 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))