1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
// Copyright 2013-2014 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 libc;
use std::io::process::{Command, ProcessOutput};
use std::io::{fs, TempDir};
use std::io;
use std::mem;
use std::os;
use std::raw;
use std::str;
use syntax::abi;

pub static METADATA_FILENAME: &'static str = "rust.metadata.bin";

pub struct Archive<'a> {
    sess: &'a Session,
    dst: Path,
}

pub struct ArchiveRO {
    ptr: ArchiveRef,
}

fn run_ar(sess: &Session, args: &str, cwd: Option<&Path>,
          paths: &[&Path]) -> ProcessOutput {
    let ar = get_ar_prog(sess);
    let mut cmd = Command::new(ar.as_slice());

    cmd.arg(args).args(paths);
    debug!("{}", cmd);

    match cwd {
        Some(p) => {
            cmd.cwd(p);
            debug!("inside {}", p.display());
        }
        None => {}
    }

    match cmd.spawn() {
        Ok(prog) => {
            let o = prog.wait_with_output().unwrap();
            if !o.status.success() {
                sess.err(format!("{} failed with: {}",
                                 cmd,
                                 o.status).as_slice());
                sess.note(format!("stdout ---\n{}",
                                  str::from_utf8(o.output
                                                  .as_slice()).unwrap())
                          .as_slice());
                sess.note(format!("stderr ---\n{}",
                                  str::from_utf8(o.error
                                                  .as_slice()).unwrap())
                          .as_slice());
                sess.abort_if_errors();
            }
            o
        },
        Err(e) => {
            sess.err(format!("could not exec `{}`: {}", ar.as_slice(),
                             e).as_slice());
            sess.abort_if_errors();
            fail!("rustc::back::archive::run_ar() should not reach this point");
        }
    }
}

impl<'a> Archive<'a> {
    /// Initializes a new static archive with the given object file
    pub fn create<'b>(sess: &'a Session, dst: &'b Path,
                      initial_object: &'b Path) -> Archive<'a> {
        run_ar(sess, "crus", None, [dst, initial_object]);
        Archive { sess: sess, dst: dst.clone() }
    }

    /// Opens an existing static archive
    pub fn open(sess: &'a Session, dst: Path) -> Archive<'a> {
        assert!(dst.exists());
        Archive { sess: sess, dst: dst }
    }

    /// 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) -> io::IoResult<()> {
        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) -> io::IoResult<()> {
        let object = format!("{}.o", name);
        let bytecode = format!("{}.bytecode.deflate", name);
        let mut ignore = vec!(bytecode.as_slice(), METADATA_FILENAME);
        if lto {
            ignore.push(object.as_slice());
        }
        self.add_archive(rlib, name, ignore.as_slice())
    }

    /// 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)]);
    }

    /// Updates all symbols in the archive (runs 'ar s' over it)
    pub fn update_symbols(&mut self) {
        run_ar(self.sess, "s", None, [&self.dst]);
    }

    /// Lists all files in an archive
    pub fn files(&self) -> Vec<String> {
        let output = run_ar(self.sess, "t", None, [&self.dst]);
        let output = str::from_utf8(output.output.as_slice()).unwrap();
        // use lines_any because windows delimits output with `\r\n` instead of
        // just `\n`
        output.lines_any().map(|s| s.to_string()).collect()
    }

    fn add_archive(&mut self, archive: &Path, name: &str,
                   skip: &[&str]) -> io::IoResult<()> {
        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 = try!(fs::readdir(loc.path()));
        let mut inputs = Vec::new();
        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);
            // LLDB (as mentioned in back::link) crashes on filenames of exactly
            // 16 bytes in length. If we're including an object file with
            // exactly 16-bytes of characters, give it some prefix so that it's
            // not 16 bytes.
            let filename = if filename.len() == 16 {
                format!("lldb-fix-{}", filename)
            } else {
                filename
            };
            let new_filename = file.with_filename(filename);
            try!(fs::rename(file, &new_filename));
            inputs.push(new_filename);
        }
        if inputs.len() == 0 { return Ok(()) }

        // Finally, add all the renamed files to this archive
        let mut args = vec!(&self.dst);
        args.extend(inputs.iter());
        run_ar(self.sess, "r", None, args.as_slice());
        Ok(())
    }

    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.target_filesearch().get_lib_path());
        let search = self.sess.opts.addl_lib_search_paths.borrow();
        for path in search.iter().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).as_slice());
    }
}

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 })
            }
        }
    }

    /// Reads 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(mem::transmute(raw::Slice {
                    data: ptr,
                    len: size as uint,
                }))
            }
        }
    }
}

impl Drop for ArchiveRO {
    fn drop(&mut self) {
        unsafe {
            llvm::LLVMRustDestroyArchive(self.ptr);
        }
    }
}