#![crate_name = "zero_compile"]
use std::path::PathBuf;
use clap::Parser;
use toolkit_zero::compiler::{compile, CrossTarget, Dependency, Input, ToolchainChannel};
#[derive(Parser)]
#[command(name = "rzc", author, version, about, long_about = None)]
struct Cli {
#[arg(long, conflicts_with = "path", value_name = "DIR")]
dir: Option<PathBuf>,
#[arg(long, conflicts_with = "dir", value_name = "FILE")]
path: Option<PathBuf>,
#[arg(long = "dep", value_name = "SPEC", requires = "path")]
deps: Vec<String>,
#[arg(long)]
release: bool,
#[arg(long)]
verbose: bool,
#[arg(long, value_name = "TARGET")]
target: Option<CrossTarget>,
#[arg(long, conflicts_with = "nightly")]
stable: bool,
#[arg(long, conflicts_with = "stable")]
nightly: bool,
}
fn parse_dep(spec: &str) -> Result<Dependency, String> {
let at_pos = spec.find('@');
let comma_pos = spec.find(',');
let use_shorthand = at_pos.is_some()
&& comma_pos.map(|c| at_pos.unwrap() < c).unwrap_or(true);
if use_shorthand {
let (name_ver, feat_str) = match spec.split_once(':') {
Some((nv, f)) => (nv, Some(f)),
None => (spec, None),
};
let (name, version) = match name_ver.split_once('@') {
Some((n, v)) => (n, Some(v)),
None => (name_ver, None),
};
if name.is_empty() {
return Err(format!("invalid dep spec (empty name): '{spec}'"));
}
let mut dep = Dependency::new(name);
if let Some(v) = version { dep = dep.version(v); }
if let Some(fs) = feat_str {
for f in fs.split(',').filter(|s| !s.is_empty()) {
dep = dep.feature(f);
}
}
return Ok(dep);
}
let mut parts = spec.splitn(2, ',');
let name = parts.next().unwrap_or("").trim();
if name.is_empty() {
return Err(format!("invalid dep spec (empty name): '{spec}'"));
}
let mut dep = Dependency::new(name);
if let Some(rest) = parts.next() {
for token in rest.split(',').map(str::trim).filter(|s| !s.is_empty()) {
if let Some((k, v)) = token.split_once('=') {
match k.trim() {
"version" => { dep = dep.version(v.trim()); }
"path" => { dep = dep.path(v.trim()); }
"git" => { dep = dep.git(v.trim()); }
"branch" => { dep = dep.branch(v.trim()); }
"tag" => { dep = dep.tag(v.trim()); }
"rev" => { dep = dep.rev(v.trim()); }
"package" => { dep = dep.package(v.trim()); }
"features" => {
for f in v.split(';').map(str::trim).filter(|s| !s.is_empty()) {
dep = dep.feature(f);
}
}
other => {
return Err(format!("unknown dep key '{other}' in spec '{spec}'"));
}
}
} else {
match token {
"no-default-features" => { dep = dep.default_features(false); }
"optional" => { dep = dep.optional(true); }
other => {
return Err(format!("unknown dep flag '{other}' in spec '{spec}'"));
}
}
}
}
}
Ok(dep)
}
fn main() {
let cli = Cli::parse();
let input = if let Some(dir) = cli.dir {
if !dir.exists() {
eprintln!("error: directory does not exist: {}", dir.display());
std::process::exit(1);
}
Input::Dir(dir)
} else if let Some(path) = cli.path {
if !path.exists() {
eprintln!("error: file does not exist: {}", path.display());
std::process::exit(1);
}
let mut deps = Vec::new();
for spec in &cli.deps {
match parse_dep(spec) {
Ok(d) => deps.push(d),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
Input::File { path, deps }
} else {
eprintln!("error: one of --dir or --path is required");
eprintln!("Try `rzc --help` for usage.");
std::process::exit(1);
};
let channel = if cli.nightly {
ToolchainChannel::Nightly
} else {
ToolchainChannel::Stable
};
if cli.verbose {
match compile(input, cli.release, true, true, cli.target, channel) {
Ok(_) => {}
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
} else {
let result = compile(input, cli.release, false, true, cli.target, channel);
match result {
Ok(Some(_)) | Ok(None) => {}
Err(toolkit_zero::compiler::CompileError::BuildFailed(out)) => {
if !out.stdout.is_empty() {
print!("{}", out.stdout);
}
if !out.stderr.is_empty() {
eprint!("{}", out.stderr);
}
eprintln!("Build failed.");
std::process::exit(1);
}
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
}