use crate::error::Result;
use std::process::Command;
use tracing::trace;
#[derive(Debug, thiserror::Error)]
enum OptimizeError {
#[error("{0} command not found")]
CommandNotFound(String),
#[error("{0}")]
AlreadyProcessed(String),
#[error("{0}")]
ProcessingFailed(String),
}
pub fn optimize_executable(file_path: &str, strip: bool, upx: bool, quiet: bool) -> Result<()> {
if !strip && !upx {
return Ok(());
}
trace!("Optimizing executable: {}", file_path);
if strip {
match run_strip(file_path) {
Ok(_) => {
if !quiet {
println!("✓ Stripped debug symbols from: {}", file_path);
}
}
Err(e) => {
if !quiet {
match e {
OptimizeError::CommandNotFound(_) => {
eprintln!("Warning: Failed to strip {}: {}", file_path, e);
eprintln!(" Make sure 'strip' is installed and available in PATH");
}
OptimizeError::AlreadyProcessed(_) => {
eprintln!("Warning: {}", e);
}
OptimizeError::ProcessingFailed(_) => {
eprintln!("Warning: Failed to strip {}: {}", file_path, e);
}
}
}
}
}
}
if upx {
match run_upx(file_path) {
Ok(_) => {
if !quiet {
println!("✓ Compressed with UPX: {}", file_path);
}
}
Err(e) => {
if !quiet {
match e {
OptimizeError::CommandNotFound(_) => {
eprintln!("Warning: Failed to compress {} with UPX: {}", file_path, e);
eprintln!(" Make sure 'upx' is installed and available in PATH");
}
OptimizeError::AlreadyProcessed(_) => {
eprintln!("Warning: {}", e);
}
OptimizeError::ProcessingFailed(_) => {
eprintln!("Warning: Failed to compress {} with UPX: {}", file_path, e);
}
}
}
}
}
}
Ok(())
}
fn run_strip(file_path: &str) -> Result<(), OptimizeError> {
trace!("Running strip on: {}", file_path);
let output = Command::new("strip").arg(file_path).output().map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
OptimizeError::CommandNotFound("strip".to_string())
} else {
OptimizeError::ProcessingFailed(format!("Failed to execute strip command: {}", e))
}
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr_lower = stderr.to_lowercase();
if stderr_lower.contains("no symbols") || stderr_lower.contains("already stripped") {
return Err(OptimizeError::AlreadyProcessed(
"File has no debug symbols or is already stripped".to_string(),
));
}
return Err(OptimizeError::ProcessingFailed(stderr.to_string()));
}
Ok(())
}
fn run_upx(file_path: &str) -> Result<(), OptimizeError> {
trace!("Running upx on: {}", file_path);
let test_output = Command::new("upx")
.arg("-t")
.arg(file_path)
.output()
.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
OptimizeError::CommandNotFound("upx".to_string())
} else {
OptimizeError::ProcessingFailed(format!(
"Failed to execute upx test command: {}",
e
))
}
})?;
if test_output.status.success() {
return Err(OptimizeError::AlreadyProcessed(
"File is already compressed with UPX".to_string(),
));
}
let test_stderr = String::from_utf8_lossy(&test_output.stderr);
if test_stderr.contains("already packed")
|| test_stderr.contains("already compressed")
|| test_stderr.contains("NotPackable")
{
return Err(OptimizeError::AlreadyProcessed(
"File is already compressed with UPX".to_string(),
));
}
let output = Command::new("upx")
.arg("--best")
.arg("--lzma")
.arg(file_path)
.output()
.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
OptimizeError::CommandNotFound("upx".to_string())
} else {
OptimizeError::ProcessingFailed(format!("Failed to execute upx command: {}", e))
}
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("already packed")
|| stderr.contains("already compressed")
|| stderr.contains("NotPackable")
{
return Err(OptimizeError::AlreadyProcessed(
"File is already compressed with UPX".to_string(),
));
}
return Err(OptimizeError::ProcessingFailed(stderr.to_string()));
}
Ok(())
}