/
archive.rs
544 lines (492 loc) · 21 KB
/
archive.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A helper class for dealing with static archives
use std::env;
use std::ffi::{CString, CStr, OsString};
use std::fs::{self, File};
use std::io::prelude::*;
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::ptr;
use std::str;
use libc;
use llvm::archive_ro::{ArchiveRO, Child};
use llvm::{self, ArchiveKind};
use rustc::session::Session;
use rustc_back::tempdir::TempDir;
pub struct ArchiveConfig<'a> {
pub sess: &'a Session,
pub dst: PathBuf,
pub src: Option<PathBuf>,
pub lib_search_paths: Vec<PathBuf>,
pub ar_prog: String,
pub command_path: OsString,
}
/// Helper for adding many files to an archive with a single invocation of
/// `ar`.
#[must_use = "must call build() to finish building the archive"]
pub struct ArchiveBuilder<'a> {
config: ArchiveConfig<'a>,
work_dir: TempDir,
removals: Vec<String>,
additions: Vec<Addition>,
should_update_symbols: bool,
src_archive: Option<Option<ArchiveRO>>,
}
enum Addition {
File {
path: PathBuf,
name_in_archive: String,
},
Archive {
archive: ArchiveRO,
archive_name: String,
skip: Box<FnMut(&str) -> bool>,
},
}
enum Action<'a> {
Remove(&'a [String]),
AddObjects(&'a [&'a PathBuf], bool),
UpdateSymbols,
}
pub fn find_library(name: &str, search_paths: &[PathBuf], sess: &Session)
-> PathBuf {
// On Windows, static libraries sometimes show up as libfoo.a and other
// times show up as foo.lib
let oslibname = format!("{}{}{}",
sess.target.target.options.staticlib_prefix,
name,
sess.target.target.options.staticlib_suffix);
let unixlibname = format!("lib{}.a", name);
for path in search_paths {
debug!("looking for {} inside {:?}", name, path);
let test = path.join(&oslibname[..]);
if test.exists() { return test }
if oslibname != unixlibname {
let test = path.join(&unixlibname[..]);
if test.exists() { return test }
}
}
sess.fatal(&format!("could not find native static library `{}`, \
perhaps an -L flag is missing?", name));
}
fn is_relevant_child(c: &Child) -> bool {
match c.name() {
Some(name) => !name.contains("SYMDEF"),
None => false,
}
}
impl<'a> ArchiveBuilder<'a> {
/// Create a new static archive, ready for modifying the archive specified
/// by `config`.
pub fn new(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
ArchiveBuilder {
config: config,
work_dir: TempDir::new("rsar").unwrap(),
removals: Vec::new(),
additions: Vec::new(),
should_update_symbols: false,
src_archive: None,
}
}
/// Removes a file from this archive
pub fn remove_file(&mut self, file: &str) {
self.removals.push(file.to_string());
}
/// Lists all files in an archive
pub fn src_files(&mut self) -> Vec<String> {
if self.src_archive().is_none() {
return Vec::new()
}
let archive = self.src_archive.as_ref().unwrap().as_ref().unwrap();
let ret = archive.iter()
.filter_map(|child| child.ok())
.filter(is_relevant_child)
.filter_map(|child| child.name())
.filter(|name| !self.removals.iter().any(|x| x == name))
.map(|name| name.to_string())
.collect();
return ret;
}
fn src_archive(&mut self) -> Option<&ArchiveRO> {
if let Some(ref a) = self.src_archive {
return a.as_ref()
}
let src = match self.config.src {
Some(ref src) => src,
None => return None,
};
self.src_archive = Some(ArchiveRO::open(src));
self.src_archive.as_ref().unwrap().as_ref()
}
/// Adds all of the contents of a native library to this archive. This will
/// search in the relevant locations for a library named `name`.
pub fn add_native_library(&mut self, name: &str) {
let location = find_library(name, &self.config.lib_search_paths,
self.config.sess);
self.add_archive(&location, name, |_| false).unwrap_or_else(|e| {
self.config.sess.fatal(&format!("failed to add native library {}: {}",
location.to_string_lossy(), e));
});
}
/// Adds all of the contents of the rlib at the specified path to this
/// archive.
///
/// This ignores adding the bytecode from the rlib, and if LTO is enabled
/// then the object file also isn't added.
pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool)
-> io::Result<()> {
// Ignoring obj file starting with the crate name
// as simple comparison is not enough - there
// might be also an extra name suffix
let obj_start = format!("{}", name);
// Ignoring all bytecode files, no matter of
// name
let bc_ext = ".bytecode.deflate";
let metadata_filename =
self.config.sess.cstore.metadata_filename().to_owned();
self.add_archive(rlib, &name[..], move |fname: &str| {
let skip_obj = lto && fname.starts_with(&obj_start)
&& fname.ends_with(".o");
skip_obj || fname.ends_with(bc_ext) || fname == metadata_filename
})
}
fn add_archive<F>(&mut self, archive: &Path, name: &str, skip: F)
-> io::Result<()>
where F: FnMut(&str) -> bool + 'static
{
let archive = match ArchiveRO::open(archive) {
Some(ar) => ar,
None => return Err(io::Error::new(io::ErrorKind::Other,
"failed to open archive")),
};
self.additions.push(Addition::Archive {
archive: archive,
archive_name: name.to_string(),
skip: Box::new(skip),
});
Ok(())
}
/// Adds an arbitrary file to this archive
pub fn add_file(&mut self, file: &Path) {
let name = file.file_name().unwrap().to_str().unwrap();
self.additions.push(Addition::File {
path: file.to_path_buf(),
name_in_archive: name.to_string(),
});
}
/// Indicate that the next call to `build` should updates all symbols in
/// the archive (run 'ar s' over it).
pub fn update_symbols(&mut self) {
self.should_update_symbols = true;
}
/// Combine the provided files, rlibs, and native libraries into a single
/// `Archive`.
pub fn build(&mut self) {
let res = match self.llvm_archive_kind() {
Some(kind) => self.build_with_llvm(kind),
None => self.build_with_ar_cmd(),
};
if let Err(e) = res {
self.config.sess.fatal(&format!("failed to build archive: {}", e));
}
}
pub fn llvm_archive_kind(&self) -> Option<ArchiveKind> {
if unsafe { llvm::LLVMVersionMinor() < 7 } {
return None
}
// Currently LLVM only supports writing archives in the 'gnu' format.
match &self.config.sess.target.target.options.archive_format[..] {
"gnu" => Some(ArchiveKind::K_GNU),
"mips64" => Some(ArchiveKind::K_MIPS64),
"bsd" => Some(ArchiveKind::K_BSD),
"coff" => Some(ArchiveKind::K_COFF),
_ => None,
}
}
pub fn using_llvm(&self) -> bool {
self.llvm_archive_kind().is_some()
}
fn build_with_ar_cmd(&mut self) -> io::Result<()> {
let removals = mem::replace(&mut self.removals, Vec::new());
let additions = mem::replace(&mut self.additions, Vec::new());
let should_update_symbols = mem::replace(&mut self.should_update_symbols,
false);
// Don't use fs::copy because libs may be installed as read-only and we
// want to modify this archive, so we use `io::copy` to not preserve
// permission bits.
if let Some(ref s) = self.config.src {
io::copy(&mut File::open(s)?,
&mut File::create(&self.config.dst)?)?;
}
if removals.len() > 0 {
self.run(None, Action::Remove(&removals));
}
let mut members = Vec::new();
for addition in additions {
match addition {
Addition::File { path, name_in_archive } => {
let dst = self.work_dir.path().join(&name_in_archive);
fs::copy(&path, &dst)?;
members.push(PathBuf::from(name_in_archive));
}
Addition::Archive { archive, archive_name, mut skip } => {
self.add_archive_members(&mut members, archive,
&archive_name, &mut *skip)?;
}
}
}
// Get an absolute path to the destination, so `ar` will work even
// though we run it from `self.work_dir`.
let mut objects = Vec::new();
let mut total_len = self.config.dst.to_string_lossy().len();
if members.is_empty() {
if should_update_symbols {
self.run(Some(self.work_dir.path()), Action::UpdateSymbols);
}
return Ok(())
}
// Don't allow the total size of `args` to grow beyond 32,000 bytes.
// Windows will raise an error if the argument string is longer than
// 32,768, and we leave a bit of extra space for the program name.
const ARG_LENGTH_LIMIT: usize = 32_000;
for member_name in &members {
let len = member_name.to_string_lossy().len();
// `len + 1` to account for the space that's inserted before each
// argument. (Windows passes command-line arguments as a single
// string, not an array of strings.)
if total_len + len + 1 > ARG_LENGTH_LIMIT {
// Add the archive members seen so far, without updating the
// symbol table.
self.run(Some(self.work_dir.path()),
Action::AddObjects(&objects, false));
objects.clear();
total_len = self.config.dst.to_string_lossy().len();
}
objects.push(member_name);
total_len += len + 1;
}
// Add the remaining archive members, and update the symbol table if
// necessary.
self.run(Some(self.work_dir.path()),
Action::AddObjects(&objects, should_update_symbols));
Ok(())
}
fn add_archive_members(&mut self, members: &mut Vec<PathBuf>,
archive: ArchiveRO, name: &str,
skip: &mut FnMut(&str) -> bool) -> io::Result<()> {
// Next, we must rename all of the inputs to "guaranteed unique names".
// We write each file into `self.work_dir` under its new unique name.
// The reason for this renaming is that archives are keyed off the name
// of the files, so if two files have the same name they will override
// one another in the archive (bad).
//
// We skip any files explicitly desired for skipping, and we also skip
// all SYMDEF files as these are just magical placeholders which get
// re-created when we make a new archive anyway.
for file in archive.iter() {
let file = file.map_err(string_to_io_error)?;
if !is_relevant_child(&file) {
continue
}
let filename = file.name().unwrap();
if skip(filename) {
continue
}
let filename = Path::new(filename).file_name().unwrap()
.to_str().unwrap();
// Archives on unix systems typically do not have slashes in
// filenames as the `ar` utility generally only uses the last
// component of a path for the filename list in the archive. On
// Windows, however, archives assembled with `lib.exe` will preserve
// the full path to the file that was placed in the archive,
// including path separators.
//
// The code below is munging paths so it'll go wrong pretty quickly
// if there's some unexpected slashes in the filename, so here we
// just chop off everything but the filename component. Note that
// this can cause duplicate filenames, but that's also handled below
// as well.
let filename = Path::new(filename).file_name().unwrap()
.to_str().unwrap();
// An archive can contain files of the same name multiple times, so
// we need to be sure to not have them overwrite one another when we
// extract them. Consequently we need to find a truly unique file
// name for us!
let mut new_filename = String::new();
for n in 0.. {
let n = if n == 0 {String::new()} else {format!("-{}", n)};
new_filename = format!("r{}-{}-{}", n, name, filename);
// LLDB (as mentioned in back::link) crashes on filenames of
// exactly
// 16 bytes in length. If we're including an object file with
// exactly 16-bytes of characters, give it some prefix so
// that it's not 16 bytes.
new_filename = if new_filename.len() == 16 {
format!("lldb-fix-{}", new_filename)
} else {
new_filename
};
let present = members.iter().filter_map(|p| {
p.file_name().and_then(|f| f.to_str())
}).any(|s| s == new_filename);
if !present {
break
}
}
let dst = self.work_dir.path().join(&new_filename);
File::create(&dst)?.write_all(file.data())?;
members.push(PathBuf::from(new_filename));
}
Ok(())
}
fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
let ar = &self.config.ar_prog;
let mut cmd = Command::new(ar);
cmd.env("PATH", &self.config.command_path);
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
self.prepare_ar_action(&mut cmd, &abs_dst, action);
info!("{:?}", cmd);
if let Some(p) = cwd {
cmd.current_dir(p);
info!("inside {:?}", p.display());
}
let sess = &self.config.sess;
match cmd.spawn() {
Ok(prog) => {
let o = prog.wait_with_output().unwrap();
if !o.status.success() {
sess.struct_err(&format!("{:?} failed with: {}", cmd, o.status))
.note(&format!("stdout ---\n{}",
str::from_utf8(&o.stdout).unwrap()))
.note(&format!("stderr ---\n{}",
str::from_utf8(&o.stderr).unwrap()))
.emit();
sess.abort_if_errors();
}
o
},
Err(e) => {
sess.fatal(&format!("could not exec `{}`: {}",
self.config.ar_prog, e));
}
}
}
fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
match action {
Action::Remove(files) => {
cmd.arg("d").arg(dst).args(files);
}
Action::AddObjects(objs, update_symbols) => {
cmd.arg(if update_symbols {"crs"} else {"crS"})
.arg(dst)
.args(objs);
}
Action::UpdateSymbols => {
cmd.arg("s").arg(dst);
}
}
}
fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
let mut archives = Vec::new();
let mut strings = Vec::new();
let mut members = Vec::new();
let removals = mem::replace(&mut self.removals, Vec::new());
unsafe {
if let Some(archive) = self.src_archive() {
for child in archive.iter() {
let child = child.map_err(string_to_io_error)?;
let child_name = match child.name() {
Some(s) => s,
None => continue,
};
if removals.iter().any(|r| r == child_name) {
continue
}
let name = CString::new(child_name)?;
members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
name.as_ptr(),
child.raw()));
strings.push(name);
}
}
for addition in mem::replace(&mut self.additions, Vec::new()) {
match addition {
Addition::File { path, name_in_archive } => {
let path = CString::new(path.to_str().unwrap())?;
let name = CString::new(name_in_archive)?;
members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
name.as_ptr(),
ptr::null_mut()));
strings.push(path);
strings.push(name);
}
Addition::Archive { archive, archive_name: _, mut skip } => {
for child in archive.iter() {
let child = child.map_err(string_to_io_error)?;
if !is_relevant_child(&child) {
continue
}
let child_name = child.name().unwrap();
if skip(child_name) {
continue
}
// It appears that LLVM's archive writer is a little
// buggy if the name we pass down isn't just the
// filename component, so chop that off here and
// pass it in.
//
// See LLVM bug 25877 for more info.
let child_name = Path::new(child_name)
.file_name().unwrap()
.to_str().unwrap();
let name = CString::new(child_name)?;
let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
name.as_ptr(),
child.raw());
members.push(m);
strings.push(name);
}
archives.push(archive);
}
}
}
let dst = self.config.dst.to_str().unwrap().as_bytes();
let dst = CString::new(dst)?;
let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
members.len() as libc::size_t,
members.as_ptr(),
self.should_update_symbols,
kind);
let ret = if r != 0 {
let err = llvm::LLVMRustGetLastError();
let msg = if err.is_null() {
"failed to write archive".to_string()
} else {
String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
.into_owned()
};
Err(io::Error::new(io::ErrorKind::Other, msg))
} else {
Ok(())
};
for member in members {
llvm::LLVMRustArchiveMemberFree(member);
}
return ret
}
}
}
fn string_to_io_error(s: String) -> io::Error {
io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
}