1 //! A helper class for dealing with static archives
3 use std::ffi::{CStr, CString};
6 use std::path::{Path, PathBuf};
10 use crate::llvm::archive_ro::{ArchiveRO, Child};
11 use crate::llvm::{self, ArchiveKind};
12 use rustc::session::Session;
13 use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder};
14 use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME, RLIB_BYTECODE_EXTENSION};
15 use rustc_span::symbol::Symbol;
17 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 LlvmArchiveBuilder<'a> {
27 config: ArchiveConfig<'a>,
28 removals: Vec<String>,
29 additions: Vec<Addition>,
30 should_update_symbols: bool,
31 src_archive: Option<Option<ArchiveRO>>,
35 File { path: PathBuf, name_in_archive: String },
36 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
40 fn path(&self) -> &Path {
42 Addition::File { path, .. } | Addition::Archive { path, .. } => path,
47 fn is_relevant_child(c: &Child<'_>) -> bool {
49 Some(name) => !name.contains("SYMDEF"),
54 fn archive_config<'a>(sess: &'a Session, output: &Path, input: Option<&Path>) -> ArchiveConfig<'a> {
55 use rustc_codegen_ssa::back::link::archive_search_paths;
58 dst: output.to_path_buf(),
59 src: input.map(|p| p.to_path_buf()),
60 lib_search_paths: archive_search_paths(sess),
64 impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
65 /// Creates a new static archive, ready for modifying the archive specified
67 fn new(sess: &'a Session, output: &Path, input: Option<&Path>) -> LlvmArchiveBuilder<'a> {
68 let config = archive_config(sess, output, input);
72 additions: Vec::new(),
73 should_update_symbols: false,
78 /// Removes a file from this archive
79 fn remove_file(&mut self, file: &str) {
80 self.removals.push(file.to_string());
83 /// Lists all files in an archive
84 fn src_files(&mut self) -> Vec<String> {
85 if self.src_archive().is_none() {
89 let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
93 .filter_map(|child| child.ok())
94 .filter(is_relevant_child)
95 .filter_map(|child| child.name())
96 .filter(|name| !self.removals.iter().any(|x| x == name))
97 .map(|name| name.to_owned())
101 /// Adds all of the contents of a native library to this archive. This will
102 /// search in the relevant locations for a library named `name`.
103 fn add_native_library(&mut self, name: Symbol) {
104 let location = find_library(name, &self.config.lib_search_paths, self.config.sess);
105 self.add_archive(&location, |_| false).unwrap_or_else(|e| {
106 self.config.sess.fatal(&format!(
107 "failed to add native library {}: {}",
108 location.to_string_lossy(),
114 /// Adds all of the contents of the rlib at the specified path to this
117 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
118 /// then the object file also isn't added.
125 ) -> io::Result<()> {
126 // Ignoring obj file starting with the crate name
127 // as simple comparison is not enough - there
128 // might be also an extra name suffix
129 let obj_start = name.to_owned();
131 self.add_archive(rlib, move |fname: &str| {
132 // Ignore bytecode/metadata files, no matter the name.
133 if fname.ends_with(RLIB_BYTECODE_EXTENSION) || fname == METADATA_FILENAME {
137 // Don't include Rust objects if LTO is enabled
138 if lto && looks_like_rust_object_file(fname) {
142 // Otherwise if this is *not* a rust object and we're skipping
143 // objects then skip this file
144 if skip_objects && (!fname.starts_with(&obj_start) || !fname.ends_with(".o")) {
148 // ok, don't skip this
153 /// Adds an arbitrary file to this archive
154 fn add_file(&mut self, file: &Path) {
155 let name = file.file_name().unwrap().to_str().unwrap();
157 .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
160 /// Indicate that the next call to `build` should update all symbols in
161 /// the archive (equivalent to running 'ar s' over it).
162 fn update_symbols(&mut self) {
163 self.should_update_symbols = true;
166 /// Combine the provided files, rlibs, and native libraries into a single
169 let kind = self.llvm_archive_kind().unwrap_or_else(|kind| {
170 self.config.sess.fatal(&format!("Don't know how to build archive of type: {}", kind))
173 if let Err(e) = self.build_with_llvm(kind) {
174 self.config.sess.fatal(&format!("failed to build archive: {}", e));
179 impl<'a> LlvmArchiveBuilder<'a> {
180 fn src_archive(&mut self) -> Option<&ArchiveRO> {
181 if let Some(ref a) = self.src_archive {
184 let src = self.config.src.as_ref()?;
185 self.src_archive = Some(ArchiveRO::open(src).ok());
186 self.src_archive.as_ref().unwrap().as_ref()
189 fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()>
191 F: FnMut(&str) -> bool + 'static,
193 let archive_ro = match ArchiveRO::open(archive) {
195 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
197 if self.additions.iter().any(|ar| ar.path() == archive) {
200 self.additions.push(Addition::Archive {
201 path: archive.to_path_buf(),
203 skip: Box::new(skip),
208 fn llvm_archive_kind(&self) -> Result<ArchiveKind, &str> {
209 let kind = &*self.config.sess.target.target.options.archive_format;
210 kind.parse().map_err(|_| kind)
213 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
214 let removals = mem::take(&mut self.removals);
215 let mut additions = mem::take(&mut self.additions);
216 let mut strings = Vec::new();
217 let mut members = Vec::new();
219 let dst = CString::new(self.config.dst.to_str().unwrap())?;
220 let should_update_symbols = self.should_update_symbols;
223 if let Some(archive) = self.src_archive() {
224 for child in archive.iter() {
225 let child = child.map_err(string_to_io_error)?;
226 let child_name = match child.name() {
230 if removals.iter().any(|r| r == child_name) {
234 let name = CString::new(child_name)?;
235 members.push(llvm::LLVMRustArchiveMemberNew(
243 for addition in &mut additions {
245 Addition::File { path, name_in_archive } => {
246 let path = CString::new(path.to_str().unwrap())?;
247 let name = CString::new(name_in_archive.clone())?;
248 members.push(llvm::LLVMRustArchiveMemberNew(
256 Addition::Archive { archive, skip, .. } => {
257 for child in archive.iter() {
258 let child = child.map_err(string_to_io_error)?;
259 if !is_relevant_child(&child) {
262 let child_name = child.name().unwrap();
263 if skip(child_name) {
267 // It appears that LLVM's archive writer is a little
268 // buggy if the name we pass down isn't just the
269 // filename component, so chop that off here and
272 // See LLVM bug 25877 for more info.
274 Path::new(child_name).file_name().unwrap().to_str().unwrap();
275 let name = CString::new(child_name)?;
276 let m = llvm::LLVMRustArchiveMemberNew(
288 let r = llvm::LLVMRustWriteArchive(
290 members.len() as libc::size_t,
291 members.as_ptr() as *const &_,
292 should_update_symbols,
295 let ret = if r.into_result().is_err() {
296 let err = llvm::LLVMRustGetLastError();
297 let msg = if err.is_null() {
298 "failed to write archive".into()
300 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
302 Err(io::Error::new(io::ErrorKind::Other, msg))
306 for member in members {
307 llvm::LLVMRustArchiveMemberFree(member);
314 fn string_to_io_error(s: String) -> io::Error {
315 io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))