use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::Command,
};
use crate::{error::Error, utils::embed_bitcode_filepath_to_object_file};
#[derive(Debug)]
pub struct RustcWrapper {
rustc_path: PathBuf,
is_silent: bool,
}
impl RustcWrapper {
pub fn new(rustc_path: PathBuf) -> Self {
Self {
rustc_path,
is_silent: false,
}
}
pub fn silence(&mut self, value: bool) -> &mut Self {
self.is_silent = value;
self
}
pub fn run<S>(&self, args: &[S]) -> Result<Option<i32>, Error>
where
S: AsRef<OsStr> + AsRef<str> + std::fmt::Debug,
{
let status = Command::new(&self.rustc_path)
.args(args)
.status()
.map_err(Error::Io)?;
if !status.success() {
return Ok(status.code());
}
let args_str: Vec<&str> = args.iter().map(|a| <S as AsRef<str>>::as_ref(a)).collect();
if should_skip_bitcode(&args_str) {
return Ok(Some(0));
}
let output_path = find_output_path(&args_str);
let output_path = match output_path {
Some(p) => PathBuf::from(p),
None => return Ok(Some(0)),
};
let bitcode_path = derive_bitcode_path(&output_path);
if !self.is_silent {
tracing::debug!(
"Generating bitcode: output={:?}, bitcode={:?}",
output_path,
bitcode_path
);
}
let bc_status = self.generate_bitcode(&args_str, &bitcode_path)?;
if bc_status != Some(0) && bc_status.is_some() {
tracing::warn!(
"Bitcode generation failed with exit code {:?}, skipping embedding",
bc_status
);
return Ok(Some(0));
}
if output_path.exists()
&& bitcode_path.exists()
&& let Err(err) =
embed_bitcode_filepath_to_object_file::<&Path>(&bitcode_path, &output_path, None)
{
tracing::warn!("Failed to embed bitcode path into object file: {}", err);
}
Ok(Some(0))
}
fn generate_bitcode(&self, args: &[&str], bitcode_path: &Path) -> Result<Option<i32>, Error> {
let mut bc_args: Vec<String> = Vec::new();
for &arg in args {
if arg.starts_with("--emit=") || arg.starts_with("--emit ") {
continue;
}
if arg == "-o" {
continue;
}
bc_args.push(arg.to_string());
}
let mut filtered_args: Vec<String> = Vec::new();
let mut skip_next = false;
for arg in &args.iter().map(|a| a.to_string()).collect::<Vec<_>>() {
if skip_next {
skip_next = false;
continue;
}
if arg == "-o" {
skip_next = true;
continue;
}
if arg.starts_with("--emit=") || arg.starts_with("--emit ") {
continue;
}
filtered_args.push(arg.clone());
}
filtered_args.push("--emit=llvm-bc".to_string());
filtered_args.push("-o".to_string());
filtered_args.push(bitcode_path.to_string_lossy().into_owned());
if !self.is_silent {
tracing::debug!("Bitcode generation args: {:?}", filtered_args);
}
let status = Command::new(&self.rustc_path)
.args(&filtered_args)
.status()
.map_err(Error::Io)?;
Ok(status.code())
}
}
fn should_skip_bitcode(args: &[&str]) -> bool {
if args.iter().any(|a| {
*a == "--version"
|| *a == "-vV"
|| a.starts_with("--print")
|| *a == "--print"
|| *a == "-V"
}) {
tracing::debug!("Skipping bitcode: query invocation");
return true;
}
let has_source = args.iter().any(|a| {
!a.starts_with('-')
&& (a.ends_with(".rs") || !a.contains('=') && !a.contains('/') && !a.contains('\\'))
});
let has_crate_root = args.iter().any(|a| a.ends_with(".rs"));
if !has_crate_root {
tracing::debug!("Skipping bitcode: no .rs source file found");
return true;
}
let emit_values: Vec<&str> = args
.iter()
.filter_map(|a| a.strip_prefix("--emit="))
.collect();
if !emit_values.is_empty() {
let emits_obj = emit_values.iter().any(|v| {
v.split(',')
.any(|e| e == "obj" || e == "link" || e == "metadata,link")
});
if !emits_obj {
tracing::debug!("Skipping bitcode: --emit does not include obj or link");
return true;
}
}
if args.iter().any(|a| {
a.starts_with("--crate-type=proc-macro") || a.starts_with("--crate-type=proc_macro")
}) {
tracing::debug!("Skipping bitcode: proc-macro crate");
return true;
}
let mut prev_was_crate_type = false;
for arg in args {
if prev_was_crate_type && (*arg == "proc-macro" || *arg == "proc_macro") {
tracing::debug!("Skipping bitcode: proc-macro crate");
return true;
}
prev_was_crate_type = *arg == "--crate-type";
}
let _ = has_source;
false
}
fn find_output_path<'a>(args: &[&'a str]) -> Option<&'a str> {
let mut prev_was_o = false;
for arg in args {
if prev_was_o {
return Some(arg);
}
prev_was_o = *arg == "-o";
}
None
}
fn derive_bitcode_path(output_path: &Path) -> PathBuf {
output_path.with_extension("bc")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_skip_bitcode_version() {
assert!(should_skip_bitcode(&["--version"]));
assert!(should_skip_bitcode(&["-vV"]));
assert!(should_skip_bitcode(&["-V"]));
}
#[test]
fn test_should_skip_bitcode_no_source() {
assert!(should_skip_bitcode(&["-o", "output", "--crate-type=lib"]));
}
#[test]
fn test_should_skip_bitcode_proc_macro() {
assert!(should_skip_bitcode(&[
"src/lib.rs",
"--crate-type=proc-macro",
"-o",
"output"
]));
}
#[test]
fn test_should_not_skip_bitcode_normal() {
assert!(!should_skip_bitcode(&[
"src/main.rs",
"--crate-type=bin",
"--emit=link",
"-o",
"output"
]));
}
#[test]
fn test_should_skip_bitcode_emit_metadata_only() {
assert!(should_skip_bitcode(&[
"src/lib.rs",
"--emit=metadata",
"-o",
"output"
]));
}
#[test]
fn test_find_output_path() {
assert_eq!(
find_output_path(&["src/main.rs", "-o", "/tmp/output"]),
Some("/tmp/output")
);
assert_eq!(find_output_path(&["src/main.rs", "--crate-type=bin"]), None);
}
#[test]
fn test_derive_bitcode_path() {
assert_eq!(
derive_bitcode_path(Path::new("/tmp/foo.o")),
PathBuf::from("/tmp/foo.bc")
);
assert_eq!(
derive_bitcode_path(Path::new("/tmp/libfoo.rlib")),
PathBuf::from("/tmp/libfoo.bc")
);
assert_eq!(
derive_bitcode_path(Path::new("/tmp/foo")),
PathBuf::from("/tmp/foo.bc")
);
}
}