use std::{fs, path::PathBuf};
use clap::Parser;
use object::Object;
use rllvm::{config::try_rllvm_config, error::Error, merge::MergeStrategy, utils::*};
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
#[derive(Parser, Debug)]
#[command(
name = "rllvm-get-bc",
about = "Extract a single bitcode file for the given input",
author = "Shengtuo Hu <h1994st@gmail.com>",
version
)]
struct ExtractionArgs {
input: PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short = 'b', long)]
build_bitcode_archive: bool,
#[arg(long, value_enum)]
merge_strategy: Option<MergeStrategy>,
#[arg(short = 'm', long)]
save_manifest: bool,
#[arg(long)]
bitcode_root: Option<PathBuf>,
#[arg(short = 'v', long, action = clap::ArgAction::Count)]
verbose: u8,
}
pub fn main() -> Result<(), Error> {
let args = ExtractionArgs::parse();
let log_level = if args.verbose == 0 {
try_rllvm_config()?.log_level()
} else {
match args.verbose {
1 => Level::WARN,
2 => Level::INFO,
3 => Level::DEBUG,
_ => Level::TRACE,
}
};
FmtSubscriber::builder()
.with_max_level(log_level)
.with_writer(std::io::stderr)
.init();
let input = &args.input;
let input_filepath = input.canonicalize().map_err(|err| {
tracing::error!(
"Failed to obtain the absolute filepath of the input: input={:?}, err={}",
input,
err
);
err
})?;
if !input_filepath.exists() {
let error_message = format!("Input file does not exist: {:?}", input_filepath);
tracing::error!("{}", error_message);
return Err(Error::MissingFile(error_message));
}
tracing::info!("Input file: {:?}", input_filepath);
let input_data = fs::read(&input_filepath).map_err(|err| {
tracing::error!(
"Failed to read the input file: input_filepath={:?}, err={}",
input_filepath,
err
);
err
})?;
let mut object_files = vec![];
let strategy = match args.merge_strategy {
Some(s) => s,
None if args.build_bitcode_archive => MergeStrategy::Archive,
None => MergeStrategy::Full,
};
let mut output_file_ext = match strategy {
MergeStrategy::Archive => "bca",
_ => "bc",
};
if let Ok(input_object_file) = object::File::parse(&*input_data) {
tracing::info!("Input object file kind: {:?}", input_object_file.kind());
object_files = vec![input_object_file];
} else if let Ok(input_archive_file) = object::read::archive::ArchiveFile::parse(&*input_data) {
tracing::info!("Input archive file kind: {:?}", input_archive_file.kind());
for member in input_archive_file.members() {
let member = member.inspect_err(|err| {
tracing::error!("Failed to obtain the archive member: err={}", err);
})?;
let member_name = String::from_utf8_lossy(member.name());
tracing::info!("{}", member_name);
let member_object_data = member.data(&*input_data).inspect_err(|err| {
tracing::error!(
"Failed to read the object data of the archive member: member={}, err={}",
member_name,
err
);
})?;
let object_file = object::File::parse(member_object_data).inspect_err(|err| {
tracing::error!(
"Failed to parse the object data of the archive member: member={}, err={}",
member_name,
err
);
})?;
object_files.push(object_file)
}
if strategy != MergeStrategy::Archive {
output_file_ext = "a.bc";
}
} else {
return Err(Error::Unknown("Unsupported file format".to_string()));
};
let input_filename = input_filepath.file_stem().unwrap().to_string_lossy();
let output_filepath = args.output.unwrap_or(PathBuf::from(format!(
"{}.{}",
input_filename, output_file_ext
)));
let bitcode_filepaths =
extract_bitcode_filepaths_from_parsed_objects(&object_files).map_err(|err| {
tracing::error!(
"Failed to extract bitcode filepaths: object_files={:?}, err={:?}",
object_files,
err
);
err
})?;
if bitcode_filepaths.is_empty() {
let error_message = format!(
"No bitcode filepaths found in the input file: {:?}",
input_filepath
);
tracing::error!("{}", error_message);
return Err(Error::MissingFile(error_message));
}
let bitcode_filepaths: Vec<PathBuf> = {
let root = args
.bitcode_root
.clone()
.unwrap_or_else(|| PathBuf::from("."));
bitcode_filepaths
.into_iter()
.map(|path| {
if path.is_absolute() {
path
} else {
root.join(path)
}
})
.collect()
};
tracing::debug!("Bitcode filepaths: {:?}", bitcode_filepaths);
if args.save_manifest {
let input_parent_dir = input_filepath.parent().unwrap();
let output_filename = output_filepath.file_name().unwrap();
let manifest_filepath =
input_parent_dir.join(format!("{}.manifest", output_filename.to_string_lossy()));
let manifest_contents = bitcode_filepaths
.iter()
.map(|bitcode_filepath| bitcode_filepath.to_string_lossy())
.collect::<Vec<_>>()
.join("\n");
fs::write(&manifest_filepath, manifest_contents).map_err(|err| {
tracing::error!(
"Failed to save the manifest file: manifest_filepath={:?}, err={}",
manifest_filepath,
err
);
err
})?;
tracing::info!("Save manifest: {:?}", manifest_filepath);
}
if let Some(code) =
rllvm::merge::merge_bitcode_files(strategy, &bitcode_filepaths, output_filepath.clone())
.map_err(|err| {
tracing::error!(
"Failed to merge ({}) bitcode files: bitcode_filepaths={:?}, err={:?}",
strategy,
bitcode_filepaths,
err
);
err
})?
&& code != 0
{
std::process::exit(code);
}
tracing::info!("Output file: {:?}", output_filepath);
Ok(())
}