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
13 use std
::ffi
::{CString, CStr, OsString}
;
16 use std
::path
::{Path, PathBuf}
;
21 use llvm
::archive_ro
::{ArchiveRO, Child}
;
22 use llvm
::{self, ArchiveKind}
;
23 use rustc
::session
::Session
;
25 pub struct ArchiveConfig
<'a
> {
26 pub sess
: &'a Session
,
28 pub src
: Option
<PathBuf
>,
29 pub lib_search_paths
: Vec
<PathBuf
>,
31 pub command_path
: OsString
,
34 /// Helper for adding many files to an archive with a single invocation of
36 #[must_use = "must call build() to finish building the archive"]
37 pub struct ArchiveBuilder
<'a
> {
38 config
: ArchiveConfig
<'a
>,
39 removals
: Vec
<String
>,
40 additions
: Vec
<Addition
>,
41 should_update_symbols
: bool
,
42 src_archive
: Option
<Option
<ArchiveRO
>>,
48 name_in_archive
: String
,
52 skip
: Box
<FnMut(&str) -> bool
>,
56 pub fn find_library(name
: &str, search_paths
: &[PathBuf
], sess
: &Session
)
58 // On Windows, static libraries sometimes show up as libfoo.a and other
59 // times show up as foo.lib
60 let oslibname
= format
!("{}{}{}",
61 sess
.target
.target
.options
.staticlib_prefix
,
63 sess
.target
.target
.options
.staticlib_suffix
);
64 let unixlibname
= format
!("lib{}.a", name
);
66 for path
in search_paths
{
67 debug
!("looking for {} inside {:?}", name
, path
);
68 let test
= path
.join(&oslibname
);
69 if test
.exists() { return test }
70 if oslibname
!= unixlibname
{
71 let test
= path
.join(&unixlibname
);
72 if test
.exists() { return test }
75 sess
.fatal(&format
!("could not find native static library `{}`, \
76 perhaps an -L flag is missing?", name
));
79 fn is_relevant_child(c
: &Child
) -> bool
{
81 Some(name
) => !name
.contains("SYMDEF"),
86 impl<'a
> ArchiveBuilder
<'a
> {
87 /// Create a new static archive, ready for modifying the archive specified
89 pub fn new(config
: ArchiveConfig
<'a
>) -> ArchiveBuilder
<'a
> {
93 additions
: Vec
::new(),
94 should_update_symbols
: false,
99 /// Removes a file from this archive
100 pub fn remove_file(&mut self, file
: &str) {
101 self.removals
.push(file
.to_string());
104 /// Lists all files in an archive
105 pub fn src_files(&mut self) -> Vec
<String
> {
106 if self.src_archive().is_none() {
109 let archive
= self.src_archive
.as_ref().unwrap().as_ref().unwrap();
110 let ret
= archive
.iter()
111 .filter_map(|child
| child
.ok())
112 .filter(is_relevant_child
)
113 .filter_map(|child
| child
.name())
114 .filter(|name
| !self.removals
.iter().any(|x
| x
== name
))
115 .map(|name
| name
.to_string())
120 fn src_archive(&mut self) -> Option
<&ArchiveRO
> {
121 if let Some(ref a
) = self.src_archive
{
124 let src
= match self.config
.src
{
125 Some(ref src
) => src
,
128 self.src_archive
= Some(ArchiveRO
::open(src
));
129 self.src_archive
.as_ref().unwrap().as_ref()
132 /// Adds all of the contents of a native library to this archive. This will
133 /// search in the relevant locations for a library named `name`.
134 pub fn add_native_library(&mut self, name
: &str) {
135 let location
= find_library(name
, &self.config
.lib_search_paths
,
137 self.add_archive(&location
, |_
| false).unwrap_or_else(|e
| {
138 self.config
.sess
.fatal(&format
!("failed to add native library {}: {}",
139 location
.to_string_lossy(), e
));
143 /// Adds all of the contents of the rlib at the specified path to this
146 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
147 /// then the object file also isn't added.
148 pub fn add_rlib(&mut self,
152 skip_objects
: bool
) -> io
::Result
<()> {
153 // Ignoring obj file starting with the crate name
154 // as simple comparison is not enough - there
155 // might be also an extra name suffix
156 let obj_start
= format
!("{}", name
);
158 // Ignoring all bytecode files, no matter of
160 let bc_ext
= ".bytecode.deflate";
161 let metadata_filename
=
162 self.config
.sess
.cstore
.metadata_filename().to_owned();
164 self.add_archive(rlib
, move |fname
: &str| {
165 if fname
.ends_with(bc_ext
) || fname
== metadata_filename
{
169 // Don't include Rust objects if LTO is enabled
170 if lto
&& fname
.starts_with(&obj_start
) && fname
.ends_with(".o") {
174 // Otherwise if this is *not* a rust object and we're skipping
175 // objects then skip this file
176 if skip_objects
&& (!fname
.starts_with(&obj_start
) || !fname
.ends_with(".o")) {
180 // ok, don't skip this
185 fn add_archive
<F
>(&mut self, archive
: &Path
, skip
: F
)
187 where F
: FnMut(&str) -> bool
+ '
static
189 let archive
= match ArchiveRO
::open(archive
) {
191 None
=> return Err(io
::Error
::new(io
::ErrorKind
::Other
,
192 "failed to open archive")),
194 self.additions
.push(Addition
::Archive
{
196 skip
: Box
::new(skip
),
201 /// Adds an arbitrary file to this archive
202 pub fn add_file(&mut self, file
: &Path
) {
203 let name
= file
.file_name().unwrap().to_str().unwrap();
204 self.additions
.push(Addition
::File
{
205 path
: file
.to_path_buf(),
206 name_in_archive
: name
.to_string(),
210 /// Indicate that the next call to `build` should updates all symbols in
211 /// the archive (run 'ar s' over it).
212 pub fn update_symbols(&mut self) {
213 self.should_update_symbols
= true;
216 /// Combine the provided files, rlibs, and native libraries into a single
218 pub fn build(&mut self) {
219 let kind
= match self.llvm_archive_kind() {
222 self.config
.sess
.fatal(&format
!("Don't know how to build archive of type: {}",
227 if let Err(e
) = self.build_with_llvm(kind
) {
228 self.config
.sess
.fatal(&format
!("failed to build archive: {}", e
));
233 fn llvm_archive_kind(&self) -> Result
<ArchiveKind
, &str> {
234 let kind
= &*self.config
.sess
.target
.target
.options
.archive_format
;
235 kind
.parse().map_err(|_
| kind
)
238 fn build_with_llvm(&mut self, kind
: ArchiveKind
) -> io
::Result
<()> {
239 let mut archives
= Vec
::new();
240 let mut strings
= Vec
::new();
241 let mut members
= Vec
::new();
242 let removals
= mem
::replace(&mut self.removals
, Vec
::new());
245 if let Some(archive
) = self.src_archive() {
246 for child
in archive
.iter() {
247 let child
= child
.map_err(string_to_io_error
)?
;
248 let child_name
= match child
.name() {
252 if removals
.iter().any(|r
| r
== child_name
) {
256 let name
= CString
::new(child_name
)?
;
257 members
.push(llvm
::LLVMRustArchiveMemberNew(ptr
::null(),
263 for addition
in mem
::replace(&mut self.additions
, Vec
::new()) {
265 Addition
::File { path, name_in_archive }
=> {
266 let path
= CString
::new(path
.to_str().unwrap())?
;
267 let name
= CString
::new(name_in_archive
)?
;
268 members
.push(llvm
::LLVMRustArchiveMemberNew(path
.as_ptr(),
274 Addition
::Archive { archive, mut skip }
=> {
275 for child
in archive
.iter() {
276 let child
= child
.map_err(string_to_io_error
)?
;
277 if !is_relevant_child(&child
) {
280 let child_name
= child
.name().unwrap();
281 if skip(child_name
) {
285 // It appears that LLVM's archive writer is a little
286 // buggy if the name we pass down isn't just the
287 // filename component, so chop that off here and
290 // See LLVM bug 25877 for more info.
291 let child_name
= Path
::new(child_name
)
292 .file_name().unwrap()
294 let name
= CString
::new(child_name
)?
;
295 let m
= llvm
::LLVMRustArchiveMemberNew(ptr
::null(),
301 archives
.push(archive
);
306 let dst
= self.config
.dst
.to_str().unwrap().as_bytes();
307 let dst
= CString
::new(dst
)?
;
308 let r
= llvm
::LLVMRustWriteArchive(dst
.as_ptr(),
309 members
.len() as libc
::size_t
,
311 self.should_update_symbols
,
313 let ret
= if r
.into_result().is_err() {
314 let err
= llvm
::LLVMRustGetLastError();
315 let msg
= if err
.is_null() {
316 "failed to write archive".to_string()
318 String
::from_utf8_lossy(CStr
::from_ptr(err
).to_bytes())
321 Err(io
::Error
::new(io
::ErrorKind
::Other
, msg
))
325 for member
in members
{
326 llvm
::LLVMRustArchiveMemberFree(member
);
333 fn string_to_io_error(s
: String
) -> io
::Error
{
334 io
::Error
::new(io
::ErrorKind
::Other
, format
!("bad archive: {}", s
))