]> git.lizzy.rs Git - rust.git/blob - src/librustc_codegen_llvm/back/archive.rs
Auto merge of #58741 - varkor:lang-lib-feature-shared-name, r=alexreg
[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::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;
16
17 pub 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 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>>,
32 }
33
34 enum Addition {
35     File {
36         path: PathBuf,
37         name_in_archive: String,
38     },
39     Archive {
40         archive: ArchiveRO,
41         skip: Box<dyn FnMut(&str) -> bool>,
42     },
43 }
44
45 fn is_relevant_child(c: &Child<'_>) -> bool {
46     match c.name() {
47         Some(name) => !name.contains("SYMDEF"),
48         None => false,
49     }
50 }
51
52 impl<'a> ArchiveBuilder<'a> {
53     /// Creates a new static archive, ready for modifying the archive specified
54     /// by `config`.
55     pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
56         ArchiveBuilder {
57             config,
58             removals: Vec::new(),
59             additions: Vec::new(),
60             should_update_symbols: false,
61             src_archive: None,
62         }
63     }
64
65     /// Removes a file from this archive
66     pub fn remove_file(&mut self, file: &str) {
67         self.removals.push(file.to_string());
68     }
69
70     /// Lists all files in an archive
71     pub fn src_files(&mut self) -> Vec<String> {
72         if self.src_archive().is_none() {
73             return Vec::new()
74         }
75
76         let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
77
78         archive.iter()
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())
84                .collect()
85     }
86
87     fn src_archive(&mut self) -> Option<&ArchiveRO> {
88         if let Some(ref a) = self.src_archive {
89             return a.as_ref()
90         }
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()
94     }
95
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,
100                                     self.config.sess);
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));
104         });
105     }
106
107     /// Adds all of the contents of the rlib at the specified path to this
108     /// archive.
109     ///
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,
113                     rlib: &Path,
114                     name: &str,
115                     lto: bool,
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();
121
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 {
125                 return true
126             }
127
128             // Don't include Rust objects if LTO is enabled
129             if lto && fname.starts_with(&obj_start) && fname.ends_with(".o") {
130                 return true
131             }
132
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")) {
136                 return true
137             }
138
139             // ok, don't skip this
140             return false
141         })
142     }
143
144     fn add_archive<F>(&mut self, archive: &Path, skip: F)
145                       -> io::Result<()>
146         where F: FnMut(&str) -> bool + 'static
147     {
148         let archive = match ArchiveRO::open(archive) {
149             Ok(ar) => ar,
150             Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
151         };
152         self.additions.push(Addition::Archive {
153             archive,
154             skip: Box::new(skip),
155         });
156         Ok(())
157     }
158
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(),
165         });
166     }
167
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;
172     }
173
174     /// Combine the provided files, rlibs, and native libraries into a single
175     /// `Archive`.
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)));
179
180         if let Err(e) = self.build_with_llvm(kind) {
181             self.config.sess.fatal(&format!("failed to build archive: {}", e));
182         }
183
184     }
185
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)
189     }
190
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();
196
197         let dst = CString::new(self.config.dst.to_str().unwrap())?;
198         let should_update_symbols = self.should_update_symbols;
199
200         unsafe {
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() {
205                         Some(s) => s,
206                         None => continue,
207                     };
208                     if removals.iter().any(|r| r == child_name) {
209                         continue
210                     }
211
212                     let name = CString::new(child_name)?;
213                     members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
214                                                                 name.as_ptr(),
215                                                                 Some(child.raw)));
216                     strings.push(name);
217                 }
218             }
219             for addition in &mut additions {
220                 match addition {
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(),
225                                                                     name.as_ptr(),
226                                                                     None));
227                         strings.push(path);
228                         strings.push(name);
229                     }
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) {
234                                 continue
235                             }
236                             let child_name = child.name().unwrap();
237                             if skip(child_name) {
238                                 continue
239                             }
240
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
244                             // pass it in.
245                             //
246                             // See LLVM bug 25877 for more info.
247                             let child_name = Path::new(child_name)
248                                                   .file_name().unwrap()
249                                                   .to_str().unwrap();
250                             let name = CString::new(child_name)?;
251                             let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
252                                                                    name.as_ptr(),
253                                                                    Some(child.raw));
254                             members.push(m);
255                             strings.push(name);
256                         }
257                     }
258                 }
259             }
260
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,
265                                                kind);
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()
270                 } else {
271                     String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
272                 };
273                 Err(io::Error::new(io::ErrorKind::Other, msg))
274             } else {
275                 Ok(())
276             };
277             for member in members {
278                 llvm::LLVMRustArchiveMemberFree(member);
279             }
280             ret
281         }
282     }
283 }
284
285 fn string_to_io_error(s: String) -> io::Error {
286     io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
287 }