|
| 1 | +use std::{ |
| 2 | + collections::{btree_map::Entry, BTreeMap}, |
| 3 | + fs::File, |
| 4 | + io::{BufRead, BufReader, BufWriter, Write}, |
| 5 | + path::PathBuf, |
| 6 | +}; |
| 7 | + |
| 8 | +use anyhow::{Context, Error, Result}; |
| 9 | +use argh::FromArgs; |
| 10 | +use object::{Object, ObjectSymbol}; |
| 11 | + |
| 12 | +#[derive(FromArgs, PartialEq, Debug)] |
| 13 | +/// Commands for processing static libraries. |
| 14 | +#[argh(subcommand, name = "ar")] |
| 15 | +pub struct Args { |
| 16 | + #[argh(subcommand)] |
| 17 | + command: SubCommand, |
| 18 | +} |
| 19 | + |
| 20 | +#[derive(FromArgs, PartialEq, Debug)] |
| 21 | +#[argh(subcommand)] |
| 22 | +enum SubCommand { |
| 23 | + Create(CreateArgs), |
| 24 | +} |
| 25 | + |
| 26 | +#[derive(FromArgs, PartialEq, Eq, Debug)] |
| 27 | +/// Creates a static library. |
| 28 | +#[argh(subcommand, name = "create")] |
| 29 | +pub struct CreateArgs { |
| 30 | + #[argh(positional)] |
| 31 | + /// output file |
| 32 | + out: PathBuf, |
| 33 | + #[argh(positional)] |
| 34 | + /// input files |
| 35 | + files: Vec<PathBuf>, |
| 36 | +} |
| 37 | + |
| 38 | +pub fn run(args: Args) -> Result<()> { |
| 39 | + match args.command { |
| 40 | + SubCommand::Create(c_args) => create(c_args), |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +fn create(args: CreateArgs) -> Result<()> { |
| 45 | + // Process response files (starting with '@') |
| 46 | + let mut files = Vec::with_capacity(args.files.len()); |
| 47 | + for path in args.files { |
| 48 | + let path_str = path.to_str().ok_or_else(|| { |
| 49 | + Error::msg(format!("'{}' is not valid UTF-8", path.to_string_lossy())) |
| 50 | + })?; |
| 51 | + match path_str.strip_prefix('@') { |
| 52 | + Some(rsp_file) => { |
| 53 | + let reader = BufReader::new( |
| 54 | + File::open(rsp_file) |
| 55 | + .with_context(|| format!("Failed to open file '{}'", rsp_file))?, |
| 56 | + ); |
| 57 | + for result in reader.lines() { |
| 58 | + let line = result?; |
| 59 | + if !line.is_empty() { |
| 60 | + files.push(PathBuf::from(line)); |
| 61 | + } |
| 62 | + } |
| 63 | + } |
| 64 | + None => { |
| 65 | + files.push(path); |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + // Build identifiers & symbol table |
| 71 | + let mut identifiers = Vec::with_capacity(files.len()); |
| 72 | + let mut symbol_table = BTreeMap::new(); |
| 73 | + for path in &files { |
| 74 | + let file_name = path.file_name().ok_or_else(|| { |
| 75 | + Error::msg(format!("'{}' is not a file path", path.to_string_lossy())) |
| 76 | + })?; |
| 77 | + let file_name = file_name.to_str().ok_or_else(|| { |
| 78 | + Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy())) |
| 79 | + })?; |
| 80 | + let identifier = file_name.as_bytes().to_vec(); |
| 81 | + identifiers.push(identifier.clone()); |
| 82 | + |
| 83 | + let entries = match symbol_table.entry(identifier) { |
| 84 | + Entry::Vacant(e) => e.insert(Vec::new()), |
| 85 | + Entry::Occupied(_) => { |
| 86 | + return Err(Error::msg(format!("Duplicate file name '{file_name}'"))) |
| 87 | + } |
| 88 | + }; |
| 89 | + let object_file = File::open(path) |
| 90 | + .with_context(|| format!("Failed to open object file '{}'", path.to_string_lossy()))?; |
| 91 | + let map = unsafe { memmap2::MmapOptions::new().map(&object_file) } |
| 92 | + .with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?; |
| 93 | + let obj = object::File::parse(map.as_ref())?; |
| 94 | + for symbol in obj.symbols() { |
| 95 | + if symbol.is_global() { |
| 96 | + entries.push(symbol.name_bytes()?.to_vec()); |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + // Write archive |
| 102 | + let out = BufWriter::new(File::create(&args.out)?); |
| 103 | + let mut builder = |
| 104 | + ar::GnuBuilder::new(out, identifiers, ar::GnuSymbolTableFormat::Size32, symbol_table)?; |
| 105 | + for path in files { |
| 106 | + builder.append_path(path)?; |
| 107 | + } |
| 108 | + builder.into_inner()?.flush()?; |
| 109 | + Ok(()) |
| 110 | +} |
0 commit comments