1 // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! A helper class for dealing with static archives
14 use std
::ffi
::{CString, CStr, OsString}
;
15 use std
::fs
::{self, File}
;
16 use std
::io
::prelude
::*;
19 use std
::path
::{Path, PathBuf}
;
20 use std
::process
::{Command, Output, Stdio}
;
24 use middle
::cstore
::CrateStore
;
27 use llvm
::archive_ro
::{ArchiveRO, Child}
;
28 use llvm
::{self, ArchiveKind}
;
29 use rustc
::session
::Session
;
30 use rustc_back
::tempdir
::TempDir
;
32 pub struct ArchiveConfig
<'a
> {
33 pub sess
: &'a Session
,
35 pub src
: Option
<PathBuf
>,
36 pub lib_search_paths
: Vec
<PathBuf
>,
38 pub command_path
: OsString
,
41 /// Helper for adding many files to an archive with a single invocation of
43 #[must_use = "must call build() to finish building the archive"]
44 pub struct ArchiveBuilder
<'a
> {
45 config
: ArchiveConfig
<'a
>,
47 removals
: Vec
<String
>,
48 additions
: Vec
<Addition
>,
49 should_update_symbols
: bool
,
50 src_archive
: Option
<Option
<ArchiveRO
>>,
56 name_in_archive
: String
,
61 skip
: Box
<FnMut(&str) -> bool
>,
67 AddObjects(&'a
[&'a PathBuf
], bool
),
71 pub fn find_library(name
: &str, search_paths
: &[PathBuf
], sess
: &Session
)
73 // On Windows, static libraries sometimes show up as libfoo.a and other
74 // times show up as foo.lib
75 let oslibname
= format
!("{}{}{}",
76 sess
.target
.target
.options
.staticlib_prefix
,
78 sess
.target
.target
.options
.staticlib_suffix
);
79 let unixlibname
= format
!("lib{}.a", name
);
81 for path
in search_paths
{
82 debug
!("looking for {} inside {:?}", name
, path
);
83 let test
= path
.join(&oslibname
[..]);
84 if test
.exists() { return test }
85 if oslibname
!= unixlibname
{
86 let test
= path
.join(&unixlibname
[..]);
87 if test
.exists() { return test }
90 sess
.fatal(&format
!("could not find native static library `{}`, \
91 perhaps an -L flag is missing?", name
));
94 fn is_relevant_child(c
: &Child
) -> bool
{
96 Some(name
) => !name
.contains("SYMDEF"),
101 impl<'a
> ArchiveBuilder
<'a
> {
102 /// Create a new static archive, ready for modifying the archive specified
104 pub fn new(config
: ArchiveConfig
<'a
>) -> ArchiveBuilder
<'a
> {
107 work_dir
: TempDir
::new("rsar").unwrap(),
108 removals
: Vec
::new(),
109 additions
: Vec
::new(),
110 should_update_symbols
: false,
115 /// Removes a file from this archive
116 pub fn remove_file(&mut self, file
: &str) {
117 self.removals
.push(file
.to_string());
120 /// Lists all files in an archive
121 pub fn src_files(&mut self) -> Vec
<String
> {
122 if self.src_archive().is_none() {
125 let archive
= self.src_archive
.as_ref().unwrap().as_ref().unwrap();
126 let ret
= archive
.iter()
127 .filter(is_relevant_child
)
128 .filter_map(|child
| child
.name())
129 .filter(|name
| !self.removals
.iter().any(|x
| x
== name
))
130 .map(|name
| name
.to_string())
135 fn src_archive(&mut self) -> Option
<&ArchiveRO
> {
136 if let Some(ref a
) = self.src_archive
{
139 let src
= match self.config
.src
{
140 Some(ref src
) => src
,
143 self.src_archive
= Some(ArchiveRO
::open(src
));
144 self.src_archive
.as_ref().unwrap().as_ref()
147 /// Adds all of the contents of a native library to this archive. This will
148 /// search in the relevant locations for a library named `name`.
149 pub fn add_native_library(&mut self, name
: &str) {
150 let location
= find_library(name
, &self.config
.lib_search_paths
,
152 self.add_archive(&location
, name
, |_
| false).unwrap_or_else(|e
| {
153 self.config
.sess
.fatal(&format
!("failed to add native library {}: {}",
154 location
.to_string_lossy(), e
));
158 /// Adds all of the contents of the rlib at the specified path to this
161 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
162 /// then the object file also isn't added.
163 pub fn add_rlib(&mut self, rlib
: &Path
, name
: &str, lto
: bool
)
165 // Ignoring obj file starting with the crate name
166 // as simple comparison is not enough - there
167 // might be also an extra name suffix
168 let obj_start
= format
!("{}", name
);
170 // Ignoring all bytecode files, no matter of
172 let bc_ext
= ".bytecode.deflate";
173 let metadata_filename
=
174 self.config
.sess
.cstore
.metadata_filename().to_owned();
176 self.add_archive(rlib
, &name
[..], move |fname
: &str| {
177 let skip_obj
= lto
&& fname
.starts_with(&obj_start
)
178 && fname
.ends_with(".o");
179 skip_obj
|| fname
.ends_with(bc_ext
) || fname
== metadata_filename
183 fn add_archive
<F
>(&mut self, archive
: &Path
, name
: &str, skip
: F
)
185 where F
: FnMut(&str) -> bool
+ '
static
187 let archive
= match ArchiveRO
::open(archive
) {
189 None
=> return Err(io
::Error
::new(io
::ErrorKind
::Other
,
190 "failed to open archive")),
192 self.additions
.push(Addition
::Archive
{
194 archive_name
: name
.to_string(),
195 skip
: Box
::new(skip
),
200 /// Adds an arbitrary file to this archive
201 pub fn add_file(&mut self, file
: &Path
) {
202 let name
= file
.file_name().unwrap().to_str().unwrap();
203 self.additions
.push(Addition
::File
{
204 path
: file
.to_path_buf(),
205 name_in_archive
: name
.to_string(),
209 /// Indicate that the next call to `build` should updates all symbols in
210 /// the archive (run 'ar s' over it).
211 pub fn update_symbols(&mut self) {
212 self.should_update_symbols
= true;
215 /// Combine the provided files, rlibs, and native libraries into a single
217 pub fn build(&mut self) {
218 let res
= match self.llvm_archive_kind() {
219 Some(kind
) => self.build_with_llvm(kind
),
220 None
=> self.build_with_ar_cmd(),
222 if let Err(e
) = res
{
223 self.config
.sess
.fatal(&format
!("failed to build archive: {}", e
));
227 pub fn llvm_archive_kind(&self) -> Option
<ArchiveKind
> {
228 if unsafe { llvm::LLVMVersionMinor() < 7 }
{
232 // Currently LLVM only supports writing archives in the 'gnu' format.
233 match &self.config
.sess
.target
.target
.options
.archive_format
[..] {
234 "gnu" => Some(ArchiveKind
::K_GNU
),
235 "mips64" => Some(ArchiveKind
::K_MIPS64
),
236 "bsd" => Some(ArchiveKind
::K_BSD
),
237 "coff" => Some(ArchiveKind
::K_COFF
),
242 pub fn using_llvm(&self) -> bool
{
243 self.llvm_archive_kind().is_some()
246 fn build_with_ar_cmd(&mut self) -> io
::Result
<()> {
247 let removals
= mem
::replace(&mut self.removals
, Vec
::new());
248 let additions
= mem
::replace(&mut self.additions
, Vec
::new());
249 let should_update_symbols
= mem
::replace(&mut self.should_update_symbols
,
252 // Don't use fs::copy because libs may be installed as read-only and we
253 // want to modify this archive, so we use `io::copy` to not preserve
255 if let Some(ref s
) = self.config
.src
{
256 try
!(io
::copy(&mut try
!(File
::open(s
)),
257 &mut try
!(File
::create(&self.config
.dst
))));
260 if removals
.len() > 0 {
261 self.run(None
, Action
::Remove(&removals
));
264 let mut members
= Vec
::new();
265 for addition
in additions
{
267 Addition
::File { path, name_in_archive }
=> {
268 let dst
= self.work_dir
.path().join(&name_in_archive
);
269 try
!(fs
::copy(&path
, &dst
));
270 members
.push(PathBuf
::from(name_in_archive
));
272 Addition
::Archive { archive, archive_name, mut skip }
=> {
273 try
!(self.add_archive_members(&mut members
, archive
,
274 &archive_name
, &mut *skip
));
279 // Get an absolute path to the destination, so `ar` will work even
280 // though we run it from `self.work_dir`.
281 let mut objects
= Vec
::new();
282 let mut total_len
= self.config
.dst
.to_string_lossy().len();
284 if members
.is_empty() {
285 if should_update_symbols
{
286 self.run(Some(self.work_dir
.path()), Action
::UpdateSymbols
);
291 // Don't allow the total size of `args` to grow beyond 32,000 bytes.
292 // Windows will raise an error if the argument string is longer than
293 // 32,768, and we leave a bit of extra space for the program name.
294 const ARG_LENGTH_LIMIT
: usize = 32_000;
296 for member_name
in &members
{
297 let len
= member_name
.to_string_lossy().len();
299 // `len + 1` to account for the space that's inserted before each
300 // argument. (Windows passes command-line arguments as a single
301 // string, not an array of strings.)
302 if total_len
+ len
+ 1 > ARG_LENGTH_LIMIT
{
303 // Add the archive members seen so far, without updating the
305 self.run(Some(self.work_dir
.path()),
306 Action
::AddObjects(&objects
, false));
309 total_len
= self.config
.dst
.to_string_lossy().len();
312 objects
.push(member_name
);
313 total_len
+= len
+ 1;
316 // Add the remaining archive members, and update the symbol table if
318 self.run(Some(self.work_dir
.path()),
319 Action
::AddObjects(&objects
, should_update_symbols
));
323 fn add_archive_members(&mut self, members
: &mut Vec
<PathBuf
>,
324 archive
: ArchiveRO
, name
: &str,
325 skip
: &mut FnMut(&str) -> bool
) -> io
::Result
<()> {
326 // Next, we must rename all of the inputs to "guaranteed unique names".
327 // We write each file into `self.work_dir` under its new unique name.
328 // The reason for this renaming is that archives are keyed off the name
329 // of the files, so if two files have the same name they will override
330 // one another in the archive (bad).
332 // We skip any files explicitly desired for skipping, and we also skip
333 // all SYMDEF files as these are just magical placeholders which get
334 // re-created when we make a new archive anyway.
335 for file
in archive
.iter().filter(is_relevant_child
) {
336 let filename
= file
.name().unwrap();
337 if skip(filename
) { continue }
338 let filename
= Path
::new(filename
).file_name().unwrap()
341 // Archives on unix systems typically do not have slashes in
342 // filenames as the `ar` utility generally only uses the last
343 // component of a path for the filename list in the archive. On
344 // Windows, however, archives assembled with `lib.exe` will preserve
345 // the full path to the file that was placed in the archive,
346 // including path separators.
348 // The code below is munging paths so it'll go wrong pretty quickly
349 // if there's some unexpected slashes in the filename, so here we
350 // just chop off everything but the filename component. Note that
351 // this can cause duplicate filenames, but that's also handled below
353 let filename
= Path
::new(filename
).file_name().unwrap()
356 // An archive can contain files of the same name multiple times, so
357 // we need to be sure to not have them overwrite one another when we
358 // extract them. Consequently we need to find a truly unique file
360 let mut new_filename
= String
::new();
362 let n
= if n
== 0 {String::new()}
else {format!("-{}
", n)};
363 new_filename = format!("r{}
-{}
-{}
", n, name, filename);
365 // LLDB (as mentioned in back::link) crashes on filenames of
367 // 16 bytes in length. If we're including an object file with
368 // exactly 16-bytes of characters, give it some prefix so
369 // that it's not 16 bytes.
370 new_filename = if new_filename.len() == 16 {
371 format!("lldb
-fix
-{}
", new_filename)
376 let present = members.iter().filter_map(|p| {
377 p.file_name().and_then(|f| f.to_str())
378 }).any(|s| s == new_filename);
383 let dst = self.work_dir.path().join(&new_filename);
384 try!(try!(File::create(&dst)).write_all(file.data()));
385 members.push(PathBuf::from(new_filename));
390 fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
391 let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
392 let ar = &self.config.ar_prog;
393 let mut cmd = Command::new(ar);
394 cmd.env("PATH
", &self.config.command_path);
395 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
396 self.prepare_ar_action(&mut cmd, &abs_dst, action);
399 if let Some(p) = cwd {
401 info!("inside {:?}
", p.display());
404 let sess = &self.config.sess;
407 let o = prog.wait_with_output().unwrap();
408 if !o.status.success() {
409 sess.err(&format!("{:?} failed with
: {}
", cmd, o.status));
410 sess.note(&format!("stdout
---\n{}
",
411 str::from_utf8(&o.stdout).unwrap()));
412 sess.note(&format!("stderr
---\n{}
",
413 str::from_utf8(&o.stderr).unwrap()));
414 sess.abort_if_errors();
419 sess.fatal(&format!("could not exec `{}`
: {}
",
420 self.config.ar_prog, e));
425 fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
427 Action::Remove(files) => {
428 cmd.arg("d
").arg(dst).args(files);
430 Action::AddObjects(objs, update_symbols) => {
431 cmd.arg(if update_symbols {"crs"} else {"crS"})
435 Action::UpdateSymbols => {
436 cmd.arg("s
").arg(dst);
441 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
442 let mut archives = Vec::new();
443 let mut strings = Vec::new();
444 let mut members = Vec::new();
445 let removals = mem::replace(&mut self.removals, Vec::new());
448 if let Some(archive) = self.src_archive() {
449 for child in archive.iter() {
450 let child_name = match child.name() {
454 if removals.iter().any(|r| r == child_name) {
458 let name = try!(CString::new(child_name));
459 members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
465 for addition in mem::replace(&mut self.additions, Vec::new()) {
467 Addition::File { path, name_in_archive } => {
468 let path = try!(CString::new(path.to_str().unwrap()));
469 let name = try!(CString::new(name_in_archive));
470 members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
476 Addition::Archive { archive, archive_name: _, mut skip } => {
477 for child in archive.iter().filter(is_relevant_child) {
478 let child_name = child.name().unwrap();
479 if skip(child_name) { continue }
481 let name = try!(CString::new(child_name));
482 let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
488 archives.push(archive);
493 let dst = self.config.dst.to_str().unwrap().as_bytes();
494 let dst = try!(CString::new(dst));
495 let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
496 members.len() as libc::size_t,
498 self.should_update_symbols,
500 let ret = if r != 0 {
501 let err = llvm::LLVMRustGetLastError();
502 let msg = if err.is_null() {
503 "failed to write archive
".to_string()
505 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
508 Err(io::Error::new(io::ErrorKind::Other, msg))
512 for member in members {
513 llvm::LLVMRustArchiveMemberFree(member);