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