use std::path::PathBuf;
use magicpak::action;
use magicpak::base::{Error, Result};
use magicpak::domain::{Bundle, Executable};
use clap::Parser;
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
#[value(rename_all = "PascalCase")]
enum LogLevel {
Off,
Error,
Warn,
Info,
Debug,
}
impl LogLevel {
fn to_level_filter(self) -> tracing_subscriber::filter::LevelFilter {
use tracing_subscriber::filter::LevelFilter;
match self {
LogLevel::Off => LevelFilter::OFF,
LogLevel::Error => LevelFilter::ERROR,
LogLevel::Warn => LevelFilter::WARN,
LogLevel::Info => LevelFilter::INFO,
LogLevel::Debug => LevelFilter::DEBUG,
}
}
}
#[derive(Parser)]
#[command(name = "magicpak")]
struct Args {
#[arg(value_name = "INPUT", required = true)]
input: Vec<PathBuf>,
#[arg(value_name = "OUTPUT")]
output: PathBuf,
#[arg(short, long, value_name = "GLOB")]
include: Vec<String>,
#[arg(short, long, value_name = "GLOB")]
exclude: Vec<String>,
#[arg(long, value_name = "PATH")]
mkdir: Vec<String>,
#[arg(short = 'r', long, value_name = "PATH")]
install_to: Option<String>,
#[arg(long, value_name = "LEVEL", default_value = "Warn")]
log_level: LogLevel,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
test: bool,
#[arg(long, value_name = "COMMAND")]
test_command: Option<String>,
#[arg(long, value_name = "CONTENT")]
test_stdin: Option<String>,
#[arg(long, value_name = "CONTENT")]
test_stdout: Option<String>,
#[arg(short, long)]
dynamic: bool,
#[arg(
long,
value_name = "ARG",
allow_hyphen_values = true,
number_of_values = 1
)]
dynamic_arg: Vec<String>,
#[arg(long, value_name = "CONTENT")]
dynamic_stdin: Option<String>,
#[arg(short, long)]
compress: bool,
#[arg(
long,
value_name = "ARG",
allow_hyphen_values = true,
number_of_values = 1
)]
upx_arg: Vec<String>,
#[arg(long, value_name = "PATH or NAME", default_value = "busybox")]
busybox: String,
#[arg(long, value_name = "PATH or NAME", default_value = "upx")]
upx: String,
#[arg(long, value_name = "PATH or NAME", default_value = "cc", env = "CC")]
cc: String,
#[clap(long)]
experimental_noload_resolver: bool,
}
fn run(args: &Args) -> Result<()> {
let mut bundle = Bundle::new();
let mut exes = args
.input
.iter()
.map(Executable::load)
.collect::<Result<Vec<_>>>()?;
for exe in &exes {
if args.experimental_noload_resolver {
action::bundle_shared_object_dependencies_noload(&mut bundle, exe, &args.cc)?;
} else {
action::bundle_shared_object_dependencies(&mut bundle, exe, &args.cc)?;
}
}
if args.dynamic {
let &[ref exe] = &exes[..] else {
return Err(Error::DynamicWithMultipleInputsUnsupported);
};
action::bundle_dynamic_dependencies(
&mut bundle,
exe,
&args.dynamic_arg,
args.dynamic_stdin.as_ref(),
)?;
}
if args.compress {
for exe in &mut exes {
action::compress_exexcutable(exe, &args.upx, &args.upx_arg)?;
}
}
for (exe, input) in exes.iter().zip(&args.input) {
action::bundle_executable(&mut bundle, exe, input, args.install_to.as_ref())?;
}
for dir in &args.mkdir {
action::make_directory(&mut bundle, dir);
}
for glob in &args.include {
if args.experimental_noload_resolver {
action::include_glob_noload(&mut bundle, glob, &args.cc)?;
} else {
action::include_glob(&mut bundle, glob, &args.cc)?;
}
}
for glob in &args.exclude {
action::exclude_glob(&mut bundle, glob)?;
}
if args.test {
let &[ref exe] = &exes[..] else {
return Err(Error::TestWithMultipleInputsUnsupported);
};
action::test(
&bundle,
exe,
args.test_command.as_ref(),
args.test_stdin.as_ref(),
args.test_stdout.as_ref(),
&args.busybox,
)?;
}
action::emit(&mut bundle, &args.output)?;
Ok(())
}
fn main() {
let args = Args::parse();
let level_filter = if args.verbose {
tracing_subscriber::filter::LevelFilter::INFO
} else {
args.log_level.to_level_filter()
};
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
tracing_subscriber::registry()
.with(level_filter)
.with(
tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_target(false)
.without_time(),
)
.init();
std::process::exit(match run(&args) {
Ok(()) => 0,
Err(e) => {
eprintln!("error: {}", e);
1
}
});
}