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}
;
25 use llvm
::archive_ro
::{ArchiveRO, Child}
;
26 use llvm
::{self, ArchiveKind}
;
27 use rustc
::metadata
::loader
::METADATA_FILENAME
;
28 use rustc
::session
::Session
;
29 use rustc_back
::tempdir
::TempDir
;
31 pub struct ArchiveConfig
<'a
> {
32 pub sess
: &'a Session
,
34 pub src
: Option
<PathBuf
>,
35 pub lib_search_paths
: Vec
<PathBuf
>,
37 pub command_path
: OsString
,
40 /// Helper for adding many files to an archive with a single invocation of
42 #[must_use = "must call build() to finish building the archive"]
43 pub struct ArchiveBuilder
<'a
> {
44 config
: ArchiveConfig
<'a
>,
46 removals
: Vec
<String
>,
47 additions
: Vec
<Addition
>,
48 should_update_symbols
: bool
,
49 src_archive
: Option
<Option
<ArchiveRO
>>,
55 name_in_archive
: String
,
60 skip
: Box
<FnMut(&str) -> bool
>,
66 AddObjects(&'a
[&'a PathBuf
], bool
),
70 pub fn find_library(name
: &str, search_paths
: &[PathBuf
], sess
: &Session
)
72 // On Windows, static libraries sometimes show up as libfoo.a and other
73 // times show up as foo.lib
74 let oslibname
= format
!("{}{}{}",
75 sess
.target
.target
.options
.staticlib_prefix
,
77 sess
.target
.target
.options
.staticlib_suffix
);
78 let unixlibname
= format
!("lib{}.a", name
);
80 for path
in search_paths
{
81 debug
!("looking for {} inside {:?}", name
, path
);
82 let test
= path
.join(&oslibname
[..]);
83 if test
.exists() { return test }
84 if oslibname
!= unixlibname
{
85 let test
= path
.join(&unixlibname
[..]);
86 if test
.exists() { return test }
89 sess
.fatal(&format
!("could not find native static library `{}`, \
90 perhaps an -L flag is missing?", name
));
93 fn is_relevant_child(c
: &Child
) -> bool
{
95 Some(name
) => !name
.contains("SYMDEF"),
100 impl<'a
> ArchiveBuilder
<'a
> {
101 /// Create a new static archive, ready for modifying the archive specified
103 pub fn new(config
: ArchiveConfig
<'a
>) -> ArchiveBuilder
<'a
> {
106 work_dir
: TempDir
::new("rsar").unwrap(),
107 removals
: Vec
::new(),
108 additions
: Vec
::new(),
109 should_update_symbols
: false,
114 /// Removes a file from this archive
115 pub fn remove_file(&mut self, file
: &str) {
116 self.removals
.push(file
.to_string());
119 /// Lists all files in an archive
120 pub fn src_files(&mut self) -> Vec
<String
> {
121 if self.src_archive().is_none() {
124 let archive
= self.src_archive
.as_ref().unwrap().as_ref().unwrap();
125 let ret
= archive
.iter()
126 .filter(is_relevant_child
)
127 .filter_map(|child
| child
.name())
128 .filter(|name
| !self.removals
.iter().any(|x
| x
== name
))
129 .map(|name
| name
.to_string())
134 fn src_archive(&mut self) -> Option
<&ArchiveRO
> {
135 if let Some(ref a
) = self.src_archive
{
138 let src
= match self.config
.src
{
139 Some(ref src
) => src
,
142 self.src_archive
= Some(ArchiveRO
::open(src
));
143 self.src_archive
.as_ref().unwrap().as_ref()
146 /// Adds all of the contents of a native library to this archive. This will
147 /// search in the relevant locations for a library named `name`.
148 pub fn add_native_library(&mut self, name
: &str) -> io
::Result
<()> {
149 let location
= find_library(name
, &self.config
.lib_search_paths
,
151 self.add_archive(&location
, name
, |_
| false)
154 /// Adds all of the contents of the rlib at the specified path to this
157 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
158 /// then the object file also isn't added.
159 pub fn add_rlib(&mut self, rlib
: &Path
, name
: &str, lto
: bool
)
161 // Ignoring obj file starting with the crate name
162 // as simple comparison is not enough - there
163 // might be also an extra name suffix
164 let obj_start
= format
!("{}", name
);
166 // Ignoring all bytecode files, no matter of
168 let bc_ext
= ".bytecode.deflate";
170 self.add_archive(rlib
, &name
[..], move |fname
: &str| {
171 let skip_obj
= lto
&& fname
.starts_with(&obj_start
)
172 && fname
.ends_with(".o");
173 skip_obj
|| fname
.ends_with(bc_ext
) || fname
== METADATA_FILENAME
177 fn add_archive
<F
>(&mut self, archive
: &Path
, name
: &str, skip
: F
)
179 where F
: FnMut(&str) -> bool
+ '
static
181 let archive
= match ArchiveRO
::open(archive
) {
183 None
=> return Err(io
::Error
::new(io
::ErrorKind
::Other
,
184 "failed to open archive")),
186 self.additions
.push(Addition
::Archive
{
188 archive_name
: name
.to_string(),
189 skip
: Box
::new(skip
),
194 /// Adds an arbitrary file to this archive
195 pub fn add_file(&mut self, file
: &Path
) {
196 let name
= file
.file_name().unwrap().to_str().unwrap();
197 self.additions
.push(Addition
::File
{
198 path
: file
.to_path_buf(),
199 name_in_archive
: name
.to_string(),
203 /// Indicate that the next call to `build` should updates all symbols in
204 /// the archive (run 'ar s' over it).
205 pub fn update_symbols(&mut self) {
206 self.should_update_symbols
= true;
209 /// Combine the provided files, rlibs, and native libraries into a single
211 pub fn build(&mut self) {
212 let res
= match self.llvm_archive_kind() {
213 Some(kind
) => self.build_with_llvm(kind
),
214 None
=> self.build_with_ar_cmd(),
216 if let Err(e
) = res
{
217 self.config
.sess
.fatal(&format
!("failed to build archive: {}", e
));
221 pub fn llvm_archive_kind(&self) -> Option
<ArchiveKind
> {
222 if unsafe { llvm::LLVMVersionMinor() < 7 }
{
226 // Currently LLVM only supports writing archives in the 'gnu' format.
227 match &self.config
.sess
.target
.target
.options
.archive_format
[..] {
228 "gnu" => Some(ArchiveKind
::K_GNU
),
229 "mips64" => Some(ArchiveKind
::K_MIPS64
),
230 "bsd" => Some(ArchiveKind
::K_BSD
),
231 "coff" => Some(ArchiveKind
::K_COFF
),
236 pub fn using_llvm(&self) -> bool
{
237 self.llvm_archive_kind().is_some()
240 fn build_with_ar_cmd(&mut self) -> io
::Result
<()> {
241 let removals
= mem
::replace(&mut self.removals
, Vec
::new());
242 let additions
= mem
::replace(&mut self.additions
, Vec
::new());
243 let should_update_symbols
= mem
::replace(&mut self.should_update_symbols
,
246 // Don't use fs::copy because libs may be installed as read-only and we
247 // want to modify this archive, so we use `io::copy` to not preserve
249 if let Some(ref s
) = self.config
.src
{
250 try
!(io
::copy(&mut try
!(File
::open(s
)),
251 &mut try
!(File
::create(&self.config
.dst
))));
254 if removals
.len() > 0 {
255 self.run(None
, Action
::Remove(&removals
));
258 let mut members
= Vec
::new();
259 for addition
in additions
{
261 Addition
::File { path, name_in_archive }
=> {
262 let dst
= self.work_dir
.path().join(&name_in_archive
);
263 try
!(fs
::copy(&path
, &dst
));
264 members
.push(PathBuf
::from(name_in_archive
));
266 Addition
::Archive { archive, archive_name, mut skip }
=> {
267 try
!(self.add_archive_members(&mut members
, archive
,
268 &archive_name
, &mut *skip
));
273 // Get an absolute path to the destination, so `ar` will work even
274 // though we run it from `self.work_dir`.
275 let mut objects
= Vec
::new();
276 let mut total_len
= self.config
.dst
.to_string_lossy().len();
278 if members
.is_empty() {
279 if should_update_symbols
{
280 self.run(Some(self.work_dir
.path()), Action
::UpdateSymbols
);
285 // Don't allow the total size of `args` to grow beyond 32,000 bytes.
286 // Windows will raise an error if the argument string is longer than
287 // 32,768, and we leave a bit of extra space for the program name.
288 const ARG_LENGTH_LIMIT
: usize = 32_000;
290 for member_name
in &members
{
291 let len
= member_name
.to_string_lossy().len();
293 // `len + 1` to account for the space that's inserted before each
294 // argument. (Windows passes command-line arguments as a single
295 // string, not an array of strings.)
296 if total_len
+ len
+ 1 > ARG_LENGTH_LIMIT
{
297 // Add the archive members seen so far, without updating the
299 self.run(Some(self.work_dir
.path()),
300 Action
::AddObjects(&objects
, false));
303 total_len
= self.config
.dst
.to_string_lossy().len();
306 objects
.push(member_name
);
307 total_len
+= len
+ 1;
310 // Add the remaining archive members, and update the symbol table if
312 self.run(Some(self.work_dir
.path()),
313 Action
::AddObjects(&objects
, should_update_symbols
));
317 fn add_archive_members(&mut self, members
: &mut Vec
<PathBuf
>,
318 archive
: ArchiveRO
, name
: &str,
319 skip
: &mut FnMut(&str) -> bool
) -> io
::Result
<()> {
320 // Next, we must rename all of the inputs to "guaranteed unique names".
321 // We write each file into `self.work_dir` under its new unique name.
322 // The reason for this renaming is that archives are keyed off the name
323 // of the files, so if two files have the same name they will override
324 // one another in the archive (bad).
326 // We skip any files explicitly desired for skipping, and we also skip
327 // all SYMDEF files as these are just magical placeholders which get
328 // re-created when we make a new archive anyway.
329 for file
in archive
.iter().filter(is_relevant_child
) {
330 let filename
= file
.name().unwrap();
331 if skip(filename
) { continue }
332 let filename
= Path
::new(filename
).file_name().unwrap()
335 // Archives on unix systems typically do not have slashes in
336 // filenames as the `ar` utility generally only uses the last
337 // component of a path for the filename list in the archive. On
338 // Windows, however, archives assembled with `lib.exe` will preserve
339 // the full path to the file that was placed in the archive,
340 // including path separators.
342 // The code below is munging paths so it'll go wrong pretty quickly
343 // if there's some unexpected slashes in the filename, so here we
344 // just chop off everything but the filename component. Note that
345 // this can cause duplicate filenames, but that's also handled below
347 let filename
= Path
::new(filename
).file_name().unwrap()
350 // An archive can contain files of the same name multiple times, so
351 // we need to be sure to not have them overwrite one another when we
352 // extract them. Consequently we need to find a truly unique file
354 let mut new_filename
= String
::new();
356 let n
= if n
== 0 {String::new()}
else {format!("-{}
", n)};
357 new_filename = format!("r{}
-{}
-{}
", n, name, filename);
359 // LLDB (as mentioned in back::link) crashes on filenames of
361 // 16 bytes in length. If we're including an object file with
362 // exactly 16-bytes of characters, give it some prefix so
363 // that it's not 16 bytes.
364 new_filename = if new_filename.len() == 16 {
365 format!("lldb
-fix
-{}
", new_filename)
370 let present = members.iter().filter_map(|p| {
371 p.file_name().and_then(|f| f.to_str())
372 }).any(|s| s == new_filename);
377 let dst = self.work_dir.path().join(&new_filename);
378 try!(try!(File::create(&dst)).write_all(file.data()));
379 members.push(PathBuf::from(new_filename));
384 fn run(&self, cwd: Option<&Path>, action: Action) -> Output {
385 let abs_dst = env::current_dir().unwrap().join(&self.config.dst);
386 let ar = &self.config.ar_prog;
387 let mut cmd = Command::new(ar);
388 cmd.env("PATH
", &self.config.command_path);
389 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
390 self.prepare_ar_action(&mut cmd, &abs_dst, action);
393 if let Some(p) = cwd {
395 info!("inside {:?}
", p.display());
398 let sess = &self.config.sess;
401 let o = prog.wait_with_output().unwrap();
402 if !o.status.success() {
403 sess.err(&format!("{:?} failed with
: {}
", cmd, o.status));
404 sess.note(&format!("stdout
---\n{}
",
405 str::from_utf8(&o.stdout).unwrap()));
406 sess.note(&format!("stderr
---\n{}
",
407 str::from_utf8(&o.stderr).unwrap()));
408 sess.abort_if_errors();
413 sess.fatal(&format!("could not exec `{}`
: {}
",
414 self.config.ar_prog, e));
419 fn prepare_ar_action(&self, cmd: &mut Command, dst: &Path, action: Action) {
421 Action::Remove(files) => {
422 cmd.arg("d
").arg(dst).args(files);
424 Action::AddObjects(objs, update_symbols) => {
425 cmd.arg(if update_symbols {"crs"} else {"crS"})
429 Action::UpdateSymbols => {
430 cmd.arg("s
").arg(dst);
435 fn build_with_llvm(&mut self, kind: ArchiveKind) -> io::Result<()> {
436 let mut archives = Vec::new();
437 let mut strings = Vec::new();
438 let mut members = Vec::new();
439 let removals = mem::replace(&mut self.removals, Vec::new());
442 if let Some(archive) = self.src_archive() {
443 for child in archive.iter() {
444 let child_name = match child.name() {
448 if removals.iter().any(|r| r == child_name) {
452 let name = try!(CString::new(child_name));
453 members.push(llvm::LLVMRustArchiveMemberNew(ptr::null(),
459 for addition in mem::replace(&mut self.additions, Vec::new()) {
461 Addition::File { path, name_in_archive } => {
462 let path = try!(CString::new(path.to_str().unwrap()));
463 let name = try!(CString::new(name_in_archive));
464 members.push(llvm::LLVMRustArchiveMemberNew(path.as_ptr(),
470 Addition::Archive { archive, archive_name: _, mut skip } => {
471 for child in archive.iter().filter(is_relevant_child) {
472 let child_name = child.name().unwrap();
473 if skip(child_name) { continue }
475 let name = try!(CString::new(child_name));
476 let m = llvm::LLVMRustArchiveMemberNew(ptr::null(),
482 archives.push(archive);
487 let dst = self.config.dst.to_str().unwrap().as_bytes();
488 let dst = try!(CString::new(dst));
489 let r = llvm::LLVMRustWriteArchive(dst.as_ptr(),
490 members.len() as libc::size_t,
492 self.should_update_symbols,
494 let ret = if r != 0 {
495 let err = llvm::LLVMRustGetLastError();
496 let msg = if err.is_null() {
497 "failed to write archive
".to_string()
499 String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
502 Err(io::Error::new(io::ErrorKind::Other, msg))
506 for member in members {
507 llvm::LLVMRustArchiveMemberFree(member);