]> git.lizzy.rs Git - rust.git/blob - src/librustc_codegen_llvm/back/archive.rs
Rollup merge of #69825 - lcnr:discriminant, r=oli-obk
[rust.git] / src / librustc_codegen_llvm / back / archive.rs
1 //! A helper class for dealing with static archives
2
3 use std::ffi::{CStr, CString};
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::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;
16
17 struct ArchiveConfig<'a> {
18     pub sess: &'a Session,
19     pub dst: PathBuf,
20     pub src: Option<PathBuf>,
21     pub lib_search_paths: Vec<PathBuf>,
22 }
23
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>>,
32 }
33
34 enum Addition {
35     File { path: PathBuf, name_in_archive: String },
36     Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
37 }
38
39 impl Addition {
40     fn path(&self) -> &Path {
41         match self {
42             Addition::File { path, .. } | Addition::Archive { path, .. } => path,
43         }
44     }
45 }
46
47 fn is_relevant_child(c: &Child<'_>) -> bool {
48     match c.name() {
49         Some(name) => !name.contains("SYMDEF"),
50         None => false,
51     }
52 }
53
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;
56     ArchiveConfig {
57         sess,
58         dst: output.to_path_buf(),
59         src: input.map(|p| p.to_path_buf()),
60         lib_search_paths: archive_search_paths(sess),
61     }
62 }
63
64 impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
65     /// Creates a new static archive, ready for modifying the archive specified
66     /// by `config`.
67     fn new(sess: &'a Session, output: &Path, input: Option<&Path>) -> LlvmArchiveBuilder<'a> {
68         let config = archive_config(sess, output, input);
69         LlvmArchiveBuilder {
70             config,
71             removals: Vec::new(),
72             additions: Vec::new(),
73             should_update_symbols: false,
74             src_archive: None,
75         }
76     }
77
78     /// Removes a file from this archive
79     fn remove_file(&mut self, file: &str) {
80         self.removals.push(file.to_string());
81     }
82
83     /// Lists all files in an archive
84     fn src_files(&mut self) -> Vec<String> {
85         if self.src_archive().is_none() {
86             return Vec::new();
87         }
88
89         let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
90
91         archive
92             .iter()
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())
98             .collect()
99     }
100
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(),
109                 e
110             ));
111         });
112     }
113
114     /// Adds all of the contents of the rlib at the specified path to this
115     /// archive.
116     ///
117     /// This ignores adding the bytecode from the rlib, and if LTO is enabled
118     /// then the object file also isn't added.
119     fn add_rlib(
120         &mut self,
121         rlib: &Path,
122         name: &str,
123         lto: bool,
124         skip_objects: bool,
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();
130
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 {
134                 return true;
135             }
136
137             // Don't include Rust objects if LTO is enabled
138             if lto && looks_like_rust_object_file(fname) {
139                 return true;
140             }
141
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")) {
145                 return true;
146             }
147
148             // ok, don't skip this
149             return false;
150         })
151     }
152
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();
156         self.additions
157             .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
158     }
159
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;
164     }
165
166     /// Combine the provided files, rlibs, and native libraries into a single
167     /// `Archive`.
168     fn build(mut self) {
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))
171         });
172
173         if let Err(e) = self.build_with_llvm(kind) {
174             self.config.sess.fatal(&format!("failed to build archive: {}", e));
175         }
176     }
177 }
178
179 impl<'a> LlvmArchiveBuilder<'a> {
180     fn src_archive(&mut self) -> Option<&ArchiveRO> {
181         if let Some(ref a) = self.src_archive {
182             return a.as_ref();
183         }
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()
187     }
188
189     fn add_archive<F>(&mut self, archive: &Path, skip: F) -> io::Result<()>
190     where
191         F: FnMut(&str) -> bool + 'static,
192     {
193         let archive_ro = match ArchiveRO::open(archive) {
194             Ok(ar) => ar,
195             Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
196         };
197         if self.additions.iter().any(|ar| ar.path() == archive) {
198             return Ok(());
199         }
200         self.additions.push(Addition::Archive {
201             path: archive.to_path_buf(),
202             archive: archive_ro,
203             skip: Box::new(skip),
204         });
205         Ok(())
206     }
207
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)
211     }
212
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();
218
219         let dst = CString::new(self.config.dst.to_str().unwrap())?;
220         let should_update_symbols = self.should_update_symbols;
221
222         unsafe {
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() {
227                         Some(s) => s,
228                         None => continue,
229                     };
230                     if removals.iter().any(|r| r == child_name) {
231                         continue;
232                     }
233
234                     let name = CString::new(child_name)?;
235                     members.push(llvm::LLVMRustArchiveMemberNew(
236                         ptr::null(),
237                         name.as_ptr(),
238                         Some(child.raw),
239                     ));
240                     strings.push(name);
241                 }
242             }
243             for addition in &mut additions {
244                 match addition {
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(
249                             path.as_ptr(),
250                             name.as_ptr(),
251                             None,
252                         ));
253                         strings.push(path);
254                         strings.push(name);
255                     }
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) {
260                                 continue;
261                             }
262                             let child_name = child.name().unwrap();
263                             if skip(child_name) {
264                                 continue;
265                             }
266
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
270                             // pass it in.
271                             //
272                             // See LLVM bug 25877 for more info.
273                             let child_name =
274                                 Path::new(child_name).file_name().unwrap().to_str().unwrap();
275                             let name = CString::new(child_name)?;
276                             let m = llvm::LLVMRustArchiveMemberNew(
277                                 ptr::null(),
278                                 name.as_ptr(),
279                                 Some(child.raw),
280                             );
281                             members.push(m);
282                             strings.push(name);
283                         }
284                     }
285                 }
286             }
287
288             let r = llvm::LLVMRustWriteArchive(
289                 dst.as_ptr(),
290                 members.len() as libc::size_t,
291                 members.as_ptr() as *const &_,
292                 should_update_symbols,
293                 kind,
294             );
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()
299                 } else {
300                     String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
301                 };
302                 Err(io::Error::new(io::ErrorKind::Other, msg))
303             } else {
304                 Ok(())
305             };
306             for member in members {
307                 llvm::LLVMRustArchiveMemberFree(member);
308             }
309             ret
310         }
311     }
312 }
313
314 fn string_to_io_error(s: String) -> io::Error {
315     io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
316 }