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}
;
16 use std
::path
::{Path, PathBuf}
;
20 use back
::bytecode
::RLIB_BYTECODE_EXTENSION
;
22 use llvm
::archive_ro
::{ArchiveRO, Child}
;
23 use llvm
::{self, ArchiveKind}
;
24 use metadata
::METADATA_FILENAME
;
25 use rustc
::session
::Session
;
27 pub struct ArchiveConfig
<'a
> {
28 pub sess
: &'a Session
,
30 pub src
: Option
<PathBuf
>,
31 pub lib_search_paths
: Vec
<PathBuf
>,
34 /// Helper for adding many files to an archive.
35 #[must_use = "must call build() to finish building the archive"]
36 pub struct ArchiveBuilder
<'a
> {
37 config
: ArchiveConfig
<'a
>,
38 removals
: Vec
<String
>,
39 additions
: Vec
<Addition
>,
40 should_update_symbols
: bool
,
41 src_archive
: Option
<Option
<ArchiveRO
>>,
47 name_in_archive
: String
,
51 skip
: Box
<FnMut(&str) -> bool
>,
55 pub fn find_library(name
: &str, search_paths
: &[PathBuf
], sess
: &Session
)
57 // On Windows, static libraries sometimes show up as libfoo.a and other
58 // times show up as foo.lib
59 let oslibname
= format
!("{}{}{}",
60 sess
.target
.target
.options
.staticlib_prefix
,
62 sess
.target
.target
.options
.staticlib_suffix
);
63 let unixlibname
= format
!("lib{}.a", name
);
65 for path
in search_paths
{
66 debug
!("looking for {} inside {:?}", name
, path
);
67 let test
= path
.join(&oslibname
);
68 if test
.exists() { return test }
69 if oslibname
!= unixlibname
{
70 let test
= path
.join(&unixlibname
);
71 if test
.exists() { return test }
74 sess
.fatal(&format
!("could not find native static library `{}`, \
75 perhaps an -L flag is missing?", name
));
78 fn is_relevant_child(c
: &Child
) -> bool
{
80 Some(name
) => !name
.contains("SYMDEF"),
85 impl<'a
> ArchiveBuilder
<'a
> {
86 /// Create a new static archive, ready for modifying the archive specified
88 pub fn new(config
: ArchiveConfig
<'a
>) -> ArchiveBuilder
<'a
> {
92 additions
: Vec
::new(),
93 should_update_symbols
: false,
98 /// Removes a file from this archive
99 pub fn remove_file(&mut self, file
: &str) {
100 self.removals
.push(file
.to_string());
103 /// Lists all files in an archive
104 pub fn src_files(&mut self) -> Vec
<String
> {
105 if self.src_archive().is_none() {
108 let archive
= self.src_archive
.as_ref().unwrap().as_ref().unwrap();
109 let ret
= archive
.iter()
110 .filter_map(|child
| child
.ok())
111 .filter(is_relevant_child
)
112 .filter_map(|child
| child
.name())
113 .filter(|name
| !self.removals
.iter().any(|x
| x
== name
))
114 .map(|name
| name
.to_string())
119 fn src_archive(&mut self) -> Option
<&ArchiveRO
> {
120 if let Some(ref a
) = self.src_archive
{
123 let src
= self.config
.src
.as_ref()?
;
124 self.src_archive
= Some(ArchiveRO
::open(src
).ok());
125 self.src_archive
.as_ref().unwrap().as_ref()
128 /// Adds all of the contents of a native library to this archive. This will
129 /// search in the relevant locations for a library named `name`.
130 pub fn add_native_library(&mut self, name
: &str) {
131 let location
= find_library(name
, &self.config
.lib_search_paths
,
133 self.add_archive(&location
, |_
| false).unwrap_or_else(|e
| {
134 self.config
.sess
.fatal(&format
!("failed to add native library {}: {}",
135 location
.to_string_lossy(), e
));
139 /// Adds all of the contents of the rlib at the specified path to this
142 /// This ignores adding the bytecode from the rlib, and if LTO is enabled
143 /// then the object file also isn't added.
144 pub fn add_rlib(&mut self,
148 skip_objects
: bool
) -> io
::Result
<()> {
149 // Ignoring obj file starting with the crate name
150 // as simple comparison is not enough - there
151 // might be also an extra name suffix
152 let obj_start
= format
!("{}", name
);
154 self.add_archive(rlib
, move |fname
: &str| {
155 // Ignore bytecode/metadata files, no matter the name.
156 if fname
.ends_with(RLIB_BYTECODE_EXTENSION
) || fname
== METADATA_FILENAME
{
160 // Don't include Rust objects if LTO is enabled
161 if lto
&& fname
.starts_with(&obj_start
) && fname
.ends_with(".o") {
165 // Otherwise if this is *not* a rust object and we're skipping
166 // objects then skip this file
167 if skip_objects
&& (!fname
.starts_with(&obj_start
) || !fname
.ends_with(".o")) {
171 // ok, don't skip this
176 fn add_archive
<F
>(&mut self, archive
: &Path
, skip
: F
)
178 where F
: FnMut(&str) -> bool
+ '
static
180 let archive
= match ArchiveRO
::open(archive
) {
182 Err(e
) => return Err(io
::Error
::new(io
::ErrorKind
::Other
, e
)),
184 self.additions
.push(Addition
::Archive
{
186 skip
: Box
::new(skip
),
191 /// Adds an arbitrary file to this archive
192 pub fn add_file(&mut self, file
: &Path
) {
193 let name
= file
.file_name().unwrap().to_str().unwrap();
194 self.additions
.push(Addition
::File
{
195 path
: file
.to_path_buf(),
196 name_in_archive
: name
.to_string(),
200 /// Indicate that the next call to `build` should update all symbols in
201 /// the archive (equivalent to running 'ar s' over it).
202 pub fn update_symbols(&mut self) {
203 self.should_update_symbols
= true;
206 /// Combine the provided files, rlibs, and native libraries into a single
208 pub fn build(&mut self) {
209 let kind
= match self.llvm_archive_kind() {
212 self.config
.sess
.fatal(&format
!("Don't know how to build archive of type: {}",
217 if let Err(e
) = self.build_with_llvm(kind
) {
218 self.config
.sess
.fatal(&format
!("failed to build archive: {}", e
));
223 fn llvm_archive_kind(&self) -> Result
<ArchiveKind
, &str> {
224 let kind
= &*self.config
.sess
.target
.target
.options
.archive_format
;
225 kind
.parse().map_err(|_
| kind
)
228 fn build_with_llvm(&mut self, kind
: ArchiveKind
) -> io
::Result
<()> {
229 let mut archives
= Vec
::new();
230 let mut strings
= Vec
::new();
231 let mut members
= Vec
::new();
232 let removals
= mem
::replace(&mut self.removals
, Vec
::new());
235 if let Some(archive
) = self.src_archive() {
236 for child
in archive
.iter() {
237 let child
= child
.map_err(string_to_io_error
)?
;
238 let child_name
= match child
.name() {
242 if removals
.iter().any(|r
| r
== child_name
) {
246 let name
= CString
::new(child_name
)?
;
247 members
.push(llvm
::LLVMRustArchiveMemberNew(ptr
::null(),
253 for addition
in mem
::replace(&mut self.additions
, Vec
::new()) {
255 Addition
::File { path, name_in_archive }
=> {
256 let path
= CString
::new(path
.to_str().unwrap())?
;
257 let name
= CString
::new(name_in_archive
)?
;
258 members
.push(llvm
::LLVMRustArchiveMemberNew(path
.as_ptr(),
264 Addition
::Archive { archive, mut skip }
=> {
265 for child
in archive
.iter() {
266 let child
= child
.map_err(string_to_io_error
)?
;
267 if !is_relevant_child(&child
) {
270 let child_name
= child
.name().unwrap();
271 if skip(child_name
) {
275 // It appears that LLVM's archive writer is a little
276 // buggy if the name we pass down isn't just the
277 // filename component, so chop that off here and
280 // See LLVM bug 25877 for more info.
281 let child_name
= Path
::new(child_name
)
282 .file_name().unwrap()
284 let name
= CString
::new(child_name
)?
;
285 let m
= llvm
::LLVMRustArchiveMemberNew(ptr
::null(),
291 archives
.push(archive
);
296 let dst
= self.config
.dst
.to_str().unwrap().as_bytes();
297 let dst
= CString
::new(dst
)?
;
298 let r
= llvm
::LLVMRustWriteArchive(dst
.as_ptr(),
299 members
.len() as libc
::size_t
,
301 self.should_update_symbols
,
303 let ret
= if r
.into_result().is_err() {
304 let err
= llvm
::LLVMRustGetLastError();
305 let msg
= if err
.is_null() {
306 "failed to write archive".to_string()
308 String
::from_utf8_lossy(CStr
::from_ptr(err
).to_bytes())
311 Err(io
::Error
::new(io
::ErrorKind
::Other
, msg
))
315 for member
in members
{
316 llvm
::LLVMRustArchiveMemberFree(member
);
323 fn string_to_io_error(s
: String
) -> io
::Error
{
324 io
::Error
::new(io
::ErrorKind
::Other
, format
!("bad archive: {}", s
))