maybe_ar_prog: Option<String>
}
+/// Helper for adding many files to an archive with a single invocation of
+/// `ar`.
+#[must_use = "must call build() to finish building the archive"]
+pub struct ArchiveBuilder<'a> {
+ archive: Archive<'a>,
+ work_dir: TempDir,
+ /// Filename of each member that should be added to the archive.
+ members: Vec<Path>,
+ should_update_symbols: bool,
+}
+
fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
args: &str, cwd: Option<&Path>,
paths: &[&Path]) -> ProcessOutput {
}
impl<'a> Archive<'a> {
- /// Initializes a new static archive with the given object file
- pub fn create<'b>(config: ArchiveConfig<'a>, initial_object: &'b Path) -> Archive<'a> {
+ fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
- run_ar(handler, &maybe_ar_prog, "crus", None, [&dst, initial_object]);
Archive {
handler: handler,
dst: dst,
/// Opens an existing static archive
pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
- let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
- assert!(dst.exists());
- Archive {
- handler: handler,
- dst: dst,
- lib_search_paths: lib_search_paths,
- os: os,
- maybe_ar_prog: maybe_ar_prog
+ let archive = Archive::new(config);
+ assert!(archive.dst.exists());
+ archive
+ }
+
+ /// Removes a file from this archive
+ pub fn remove_file(&mut self, file: &str) {
+ run_ar(self.handler, &self.maybe_ar_prog, "d", None, [&self.dst, &Path::new(file)]);
+ }
+
+ /// Lists all files in an archive
+ pub fn files(&self) -> Vec<String> {
+ let output = run_ar(self.handler, &self.maybe_ar_prog, "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()
+ }
+
+ /// Creates an `ArchiveBuilder` for adding files to this archive.
+ pub fn extend(self) -> ArchiveBuilder<'a> {
+ ArchiveBuilder::new(self)
+ }
+}
+
+impl<'a> ArchiveBuilder<'a> {
+ fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
+ ArchiveBuilder {
+ archive: archive,
+ work_dir: TempDir::new("rsar").unwrap(),
+ members: vec![],
+ should_update_symbols: false,
}
}
+ /// Create a new static archive, ready for adding files.
+ pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
+ let archive = Archive::new(config);
+ ArchiveBuilder::new(archive)
+ }
+
/// 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<()> {
}
/// 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.handler, &self.maybe_ar_prog, cmd, None, [&self.dst, file]);
- }
-
- /// Removes a file from this archive
- pub fn remove_file(&mut self, file: &str) {
- run_ar(self.handler, &self.maybe_ar_prog, "d", None, [&self.dst, &Path::new(file)]);
+ pub fn add_file(&mut self, file: &Path) -> io::IoResult<()> {
+ let filename = Path::new(file.filename().unwrap());
+ let new_file = self.work_dir.path().join(&filename);
+ try!(fs::copy(file, &new_file));
+ self.members.push(filename);
+ Ok(())
}
- /// Updates all symbols in the archive (runs 'ar s' over it)
+ /// Indicate that the next call to `build` should updates all symbols in
+ /// the archive (run 'ar s' over it).
pub fn update_symbols(&mut self) {
- run_ar(self.handler, &self.maybe_ar_prog, "s", None, [&self.dst]);
+ self.should_update_symbols = true;
}
- /// Lists all files in an archive
- pub fn files(&self) -> Vec<String> {
- let output = run_ar(self.handler, &self.maybe_ar_prog, "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()
+ /// Combine the provided files, rlibs, and native libraries into a single
+ /// `Archive`.
+ pub fn build(self) -> Archive<'a> {
+ // Get an absolute path to the destination, so `ar` will work even
+ // though we run it from `self.work_dir`.
+ let abs_dst = os::getcwd().join(&self.archive.dst);
+ assert!(!abs_dst.is_relative());
+ let mut args = vec![&abs_dst];
+ let mut total_len = abs_dst.as_vec().len();
+
+ if self.members.is_empty() {
+ // OSX `ar` does not allow using `r` with no members, but it does
+ // allow running `ar s file.a` to update symbols only.
+ if self.should_update_symbols {
+ run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
+ "s", Some(self.work_dir.path()), args.as_slice());
+ }
+ return self.archive;
+ }
+
+ // Don't allow the total size of `args` to grow beyond 32,000 bytes.
+ // Windows will raise an error if the argument string is longer than
+ // 32,768, and we leave a bit of extra space for the program name.
+ static ARG_LENGTH_LIMIT: uint = 32000;
+
+ for member_name in self.members.iter() {
+ let len = member_name.as_vec().len();
+
+ // `len + 1` to account for the space that's inserted before each
+ // argument. (Windows passes command-line arguments as a single
+ // string, not an array of strings.)
+ if total_len + len + 1 > ARG_LENGTH_LIMIT {
+ // Add the archive members seen so far, without updating the
+ // symbol table (`S`).
+ run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
+ "cruS", Some(self.work_dir.path()), args.as_slice());
+
+ args.clear();
+ args.push(&abs_dst);
+ total_len = abs_dst.as_vec().len();
+ }
+
+ args.push(member_name);
+ total_len += len + 1;
+ }
+
+ // Add the remaining archive members, and update the symbol table if
+ // necessary.
+ let flags = if self.should_update_symbols { "crus" } else { "cruS" };
+ run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
+ flags, Some(self.work_dir.path()), args.as_slice());
+
+ self.archive
}
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
+ // First, extract the contents of the archive to a temporary directory.
+ // We don't unpack directly into `self.work_dir` due to the possibility
+ // of filename collisions.
let archive = os::make_absolute(archive);
- run_ar(self.handler, &self.maybe_ar_prog, "x", Some(loc.path()), [&archive]);
+ run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
+ "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 move each file into `self.work_dir` under its new unique name.
+ // The reason for this renaming 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 }
} else {
filename
};
- let new_filename = file.with_filename(filename);
+ let new_filename = self.work_dir.path().join(filename.as_slice());
try!(fs::rename(file, &new_filename));
- inputs.push(new_filename);
+ self.members.push(Path::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.handler, &self.maybe_ar_prog, "r", None, args.as_slice());
Ok(())
}
fn find_library(&self, name: &str) -> Path {
- let (osprefix, osext) = match self.os {
+ let (osprefix, osext) = match self.archive.os {
abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
};
// On Windows, static libraries sometimes show up as libfoo.a and other
let oslibname = format!("{}{}.{}", osprefix, name, osext);
let unixlibname = format!("lib{}.a", name);
- for path in self.lib_search_paths.iter() {
+ for path in self.archive.lib_search_paths.iter() {
debug!("looking for {} inside {}", name, path.display());
let test = path.join(oslibname.as_slice());
if test.exists() { return test }
if test.exists() { return test }
}
}
- self.handler.fatal(format!("could not find native static library `{}`, \
- perhaps an -L flag is missing?",
- name).as_slice());
+ self.archive.handler.fatal(format!("could not find native static library `{}`, \
+ perhaps an -L flag is missing?",
+ name).as_slice());
}
}