rust/src/librustc/back/archive.rs
2014-01-06 13:04:26 +09:00

257 lines
9.2 KiB
Rust

// Copyright 2013 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 back::link::{get_ar_prog};
use driver::session::Session;
use metadata::filesearch;
use lib::llvm::{ArchiveRef, llvm};
use std::cast;
use std::io::fs;
use std::libc;
use std::os;
use std::run::{ProcessOptions, Process, ProcessOutput};
use std::str;
use std::unstable::raw;
use extra::tempfile::TempDir;
use syntax::abi;
pub static METADATA_FILENAME: &'static str = "rust.metadata.bin";
pub struct Archive {
priv sess: Session,
priv dst: Path,
}
pub struct ArchiveRO {
priv ptr: ArchiveRef,
}
fn run_ar(sess: Session, args: &str, cwd: Option<&Path>,
paths: &[&Path]) -> ProcessOutput {
let ar = get_ar_prog(sess);
let mut args = ~[args.to_owned()];
let mut paths = paths.iter().map(|p| p.as_str().unwrap().to_owned());
args.extend(&mut paths);
let mut opts = ProcessOptions::new();
opts.dir = cwd;
debug!("{} {}", ar, args.connect(" "));
match cwd {
Some(p) => { debug!("inside {}", p.display()); }
None => {}
}
let mut opt_prog = Process::new(ar, args.as_slice(), opts);
match opt_prog {
Some(ref mut prog) => {
let o = prog.finish_with_output();
if !o.status.success() {
sess.err(format!("{} {} failed with: {}", ar, args.connect(" "),
o.status));
sess.note(format!("stdout ---\n{}", str::from_utf8(o.output)));
sess.note(format!("stderr ---\n{}", str::from_utf8(o.error)));
sess.abort_if_errors();
}
o
},
None => {
sess.err(format!("could not exec `{}`", ar));
sess.abort_if_errors();
fail!("rustc::back::archive::run_ar() should not reach this point");
}
}
}
impl Archive {
/// Initializes a new static archive with the given object file
pub fn create<'a>(sess: Session, dst: &'a Path,
initial_object: &'a Path) -> Archive {
run_ar(sess, "crus", None, [dst, initial_object]);
Archive { sess: sess, dst: dst.clone() }
}
/// Opens an existing static archive
pub fn open(sess: Session, dst: Path) -> Archive {
assert!(dst.exists());
Archive { sess: sess, dst: dst }
}
/// Read a file in the archive
pub fn read(&self, file: &str) -> ~[u8] {
// Apparently if "ar p" is used on windows, it generates a corrupt file
// which has bad headers and LLVM will immediately choke on it
if cfg!(windows) && cfg!(windows) { // FIXME(#10734) double-and
let loc = TempDir::new("rsar").unwrap();
let archive = os::make_absolute(&self.dst);
run_ar(self.sess, "x", Some(loc.path()), [&archive,
&Path::new(file)]);
fs::File::open(&loc.path().join(file)).read_to_end()
} else {
run_ar(self.sess, "p", None, [&self.dst, &Path::new(file)]).output
}
}
/// 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 = self.find_library(name);
self.add_archive(&location, name, []);
}
/// 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) {
let object = format!("{}.o", name);
let bytecode = format!("{}.bc", name);
let mut ignore = ~[METADATA_FILENAME, bytecode.as_slice()];
if lto {
ignore.push(object.as_slice());
}
self.add_archive(rlib, name, ignore);
}
/// Adds an arbitrary file to this archive
pub fn add_file(&mut self, file: &Path, has_symbols: bool) {
let cmd = if has_symbols {"r"} else {"rS"};
run_ar(self.sess, cmd, None, [&self.dst, file]);
}
/// Removes a file from this archive
pub fn remove_file(&mut self, file: &str) {
run_ar(self.sess, "d", None, [&self.dst, &Path::new(file)]);
}
/// Update all symbols in the archive (runs 'ar s' over it)
pub fn update_symbols(&mut self) {
run_ar(self.sess, "s", None, [&self.dst]);
}
/// List all files in an archive
pub fn files(&self) -> ~[~str] {
let output = run_ar(self.sess, "t", None, [&self.dst]);
str::from_utf8(output.output).lines().map(|s| s.to_owned()).collect()
}
fn add_archive(&mut self, archive: &Path, name: &str, skip: &[&str]) {
let loc = TempDir::new("rsar").unwrap();
// First, extract the contents of the archive to a temporary directory
let archive = os::make_absolute(archive);
run_ar(self.sess, "x", Some(loc.path()), [&archive]);
// Next, we must rename all of the inputs to "guaranteed unique names".
// The reason for this 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.
let files = fs::readdir(loc.path());
let mut inputs = ~[];
for file in files.iter() {
let filename = file.filename_str().unwrap();
if skip.iter().any(|s| *s == filename) { continue }
if filename.contains(".SYMDEF") { continue }
let filename = format!("r-{}-{}", name, filename);
let new_filename = file.with_filename(filename);
fs::rename(file, &new_filename);
inputs.push(new_filename);
}
// Finally, add all the renamed files to this archive
let mut args = ~[&self.dst];
args.extend(&mut inputs.iter());
run_ar(self.sess, "r", None, args.as_slice());
}
fn find_library(&self, name: &str) -> Path {
let (osprefix, osext) = match self.sess.targ_cfg.os {
abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
};
// On windows, static libraries sometimes show up as libfoo.a and other
// times show up as foo.lib
let oslibname = format!("{}{}.{}", osprefix, name, osext);
let unixlibname = format!("lib{}.a", name);
let mut rustpath = filesearch::rust_path();
rustpath.push(self.sess.filesearch.get_target_lib_path());
let addl_lib_search_paths = self.sess
.opts
.addl_lib_search_paths
.borrow();
let path = addl_lib_search_paths.get().iter();
for path in path.chain(rustpath.iter()) {
debug!("looking for {} inside {}", name, path.display());
let test = path.join(oslibname.as_slice());
if test.exists() { return test }
if oslibname != unixlibname {
let test = path.join(unixlibname.as_slice());
if test.exists() { return test }
}
}
self.sess.fatal(format!("could not find native static library `{}`, \
perhaps an -L flag is missing?", name));
}
}
impl ArchiveRO {
/// Opens a static archive for read-only purposes. This is more optimized
/// than the `open` method because it uses LLVM's internal `Archive` class
/// rather than shelling out to `ar` for everything.
///
/// If this archive is used with a mutable method, then an error will be
/// raised.
pub fn open(dst: &Path) -> Option<ArchiveRO> {
unsafe {
let ar = dst.with_c_str(|dst| {
llvm::LLVMRustOpenArchive(dst)
});
if ar.is_null() {
None
} else {
Some(ArchiveRO { ptr: ar })
}
}
}
/// Read a file in the archive
pub fn read<'a>(&'a self, file: &str) -> Option<&'a [u8]> {
unsafe {
let mut size = 0 as libc::size_t;
let ptr = file.with_c_str(|file| {
llvm::LLVMRustArchiveReadSection(self.ptr, file, &mut size)
});
if ptr.is_null() {
None
} else {
Some(cast::transmute(raw::Slice {
data: ptr,
len: size as uint,
}))
}
}
}
}
impl Drop for ArchiveRO {
fn drop(&mut self) {
unsafe {
llvm::LLVMRustDestroyArchive(self.ptr);
}
}
}