use anyhow::Result;
use clap::{Parser, Subcommand};
use inquire::{MultiSelect, Select, Confirm};
use xcargo::build::{Builder, BuildOptions};
use xcargo::config::Config;
use xcargo::output::{helpers, tips};
use xcargo::target::Target;
use xcargo::toolchain::ToolchainManager;
use std::path::Path;
#[derive(Parser)]
#[command(name = "xcargo")]
#[command(author, version, about, long_about = None)]
#[command(after_help = "TIP: Run 'xcargo build --target <triple>' to cross-compile")]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Subcommand)]
enum Commands {
Build {
#[arg(short, long)]
target: Option<String>,
#[arg(long, conflicts_with = "target")]
all: bool,
#[arg(short, long)]
release: bool,
#[arg(long)]
toolchain: Option<String>,
#[arg(last = true)]
cargo_args: Vec<String>,
},
Target {
#[command(subcommand)]
action: TargetAction,
},
Init {
#[arg(short, long)]
interactive: bool,
},
Config {
#[arg(long)]
default: bool,
},
Version,
}
#[derive(Subcommand)]
enum TargetAction {
Add {
target: String,
#[arg(long, default_value = "stable")]
toolchain: String,
},
List {
#[arg(long)]
installed: bool,
#[arg(long)]
toolchain: Option<String>,
},
Info {
target: String,
},
}
fn run_basic_setup() -> Result<()> {
helpers::section("Initialize xcargo");
if Path::new("xcargo.toml").exists() {
helpers::warning("xcargo.toml already exists");
let overwrite = Confirm::new("Overwrite existing configuration?")
.with_default(false)
.prompt()?;
if !overwrite {
helpers::info("Setup cancelled");
return Ok(());
}
}
let host = Target::detect_host()?;
let mut config = Config::default();
config.targets.default = vec![host.triple.clone()];
config.save("xcargo.toml")?;
helpers::success("Created xcargo.toml with default configuration");
helpers::tip(format!("Default target: {}", host.triple));
helpers::hint("Use 'xcargo init --interactive' for guided setup");
Ok(())
}
fn run_interactive_setup() -> Result<()> {
use xcargo::output::colors;
println!("\n{}{}✨ xcargo Interactive Setup{}", colors::BOLD, colors::CYAN, colors::RESET);
println!("{}Let's configure cross-compilation for your project!{}\n", colors::DIM, colors::RESET);
if Path::new("xcargo.toml").exists() {
helpers::warning("xcargo.toml already exists");
let overwrite = Confirm::new("Overwrite existing configuration?")
.with_default(false)
.prompt()?;
if !overwrite {
helpers::info("Setup cancelled");
return Ok(());
}
}
let host = Target::detect_host()?;
helpers::success(format!("Detected host platform: {}", host.triple));
println!();
let target_options = vec![
("Linux x86_64", "x86_64-unknown-linux-gnu"),
("Linux x86_64 (musl)", "x86_64-unknown-linux-musl"),
("Linux ARM64", "aarch64-unknown-linux-gnu"),
("Windows x86_64 (GNU)", "x86_64-pc-windows-gnu"),
("Windows x86_64 (MSVC)", "x86_64-pc-windows-msvc"),
("macOS x86_64", "x86_64-apple-darwin"),
("macOS ARM64 (M1/M2)", "aarch64-apple-darwin"),
("WebAssembly", "wasm32-unknown-unknown"),
];
let selected_names = MultiSelect::new(
"Which targets do you want to build for?",
target_options.iter().map(|(name, _)| *name).collect()
)
.with_help_message("Use ↑↓ to navigate, Space to select, Enter to confirm")
.prompt()?;
let selected_targets: Vec<String> = selected_names
.iter()
.filter_map(|&selected_name| {
target_options.iter()
.find(|(name, _)| name == &selected_name)
.map(|(_, triple)| triple.to_string())
})
.collect();
if selected_targets.is_empty() {
helpers::warning("No targets selected, using host target");
}
println!();
let parallel = Confirm::new("Enable parallel builds?")
.with_default(true)
.with_help_message("Build multiple targets concurrently for faster builds")
.prompt()?;
let cache = Confirm::new("Enable build caching?")
.with_default(true)
.with_help_message("Cache build artifacts to speed up subsequent builds")
.prompt()?;
let container_options = vec![
"Auto (use containers only when necessary)",
"Always use containers",
"Never use containers",
];
let container_choice = Select::new(
"Container build strategy:",
container_options
)
.with_help_message("Containers ensure reproducible builds")
.prompt()?;
let use_when = match container_choice {
"Auto (use containers only when necessary)" => "target.os != host.os",
"Always use containers" => "always",
"Never use containers" => "never",
_ => "target.os != host.os",
};
println!();
helpers::progress("Creating configuration...");
let mut config = Config::default();
let host_triple = host.triple.clone();
config.targets.default = if selected_targets.is_empty() {
vec![host_triple.clone()]
} else {
selected_targets.clone()
};
config.build.parallel = parallel;
config.build.cache = cache;
config.container.use_when = use_when.to_string();
config.save("xcargo.toml")?;
println!();
helpers::success("✨ Configuration created successfully!");
println!();
helpers::section("Configuration Summary");
println!("Targets: {}", selected_targets.join(", "));
println!("Parallel builds: {}", if parallel { "enabled" } else { "disabled" });
println!("Build cache: {}", if cache { "enabled" } else { "disabled" });
println!("Container strategy: {}", use_when);
println!();
helpers::section("Next Steps");
helpers::tip("Run 'xcargo build' to build for your host platform");
helpers::tip("Run 'xcargo build --all' to build for all configured targets");
helpers::tip("Run 'xcargo target add <triple>' to add more targets");
println!();
let install_now = Confirm::new("Install selected targets now?")
.with_default(true)
.prompt()?;
if install_now && !selected_targets.is_empty() {
println!();
helpers::progress("Installing targets...");
let manager = ToolchainManager::new()?;
for target in &selected_targets {
if target != &host_triple {
match manager.ensure_target("stable", target) {
Ok(()) => helpers::success(format!("Installed {}", target)),
Err(e) => helpers::warning(format!("Failed to install {}: {}", target, e)),
}
}
}
println!();
helpers::success("Setup complete! You're ready to cross-compile 🚀");
} else {
helpers::success("Setup complete! Install targets later with 'xcargo target add <triple>'");
}
Ok(())
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Build {
target,
all,
release,
toolchain,
cargo_args,
} => {
let builder = Builder::new()?;
let options = BuildOptions {
target: target.clone(),
release,
cargo_args,
toolchain,
verbose: cli.verbose,
};
if all {
let config = Config::discover()?.map(|(c, _)| c).unwrap_or_default();
if config.targets.default.is_empty() {
helpers::error("No default targets configured");
helpers::hint("Add targets to xcargo.toml: [targets] default = [\"x86_64-unknown-linux-gnu\"]");
helpers::tip(tips::CONFIG_FILE);
std::process::exit(1);
}
builder.build_all(&config.targets.default, &options)?;
} else {
builder.build(&options)?;
}
}
Commands::Target { action } => match action {
TargetAction::Add { target, toolchain } => {
helpers::section("Add Target");
let manager = ToolchainManager::new()?;
let target_triple = Target::resolve_alias(&target)?;
helpers::progress(format!(
"Adding target {} to toolchain {}...",
target_triple, toolchain
));
manager.install_target(&toolchain, &target_triple)?;
helpers::success(format!("Target {} added successfully", target_triple));
helpers::tip(format!(
"Use 'xcargo build --target {}' to build for this target",
target_triple
));
}
TargetAction::List { installed, toolchain } => {
helpers::section("Available Targets");
if installed {
let manager = ToolchainManager::new()?;
let tc = toolchain.unwrap_or_else(|| "stable".to_string());
helpers::info(format!("Installed targets for toolchain '{}':", tc));
println!();
match manager.list_targets(&tc) {
Ok(targets) => {
if targets.is_empty() {
println!(" No targets installed");
} else {
for target in targets {
println!(" • {}", target);
}
}
}
Err(e) => {
helpers::error(format!("Failed to list targets: {}", e));
std::process::exit(1);
}
}
} else {
println!("Common cross-compilation targets:\n");
println!("Linux:");
println!(" • x86_64-unknown-linux-gnu (Linux x86_64)");
println!(" • x86_64-unknown-linux-musl (Linux x86_64, statically linked)");
println!(" • aarch64-unknown-linux-gnu (Linux ARM64)");
println!();
println!("Windows:");
println!(" • x86_64-pc-windows-gnu (Windows x86_64, MinGW)");
println!(" • x86_64-pc-windows-msvc (Windows x86_64, MSVC)");
println!();
println!("macOS:");
println!(" • x86_64-apple-darwin (macOS x86_64)");
println!(" • aarch64-apple-darwin (macOS ARM64, M1/M2)");
println!();
helpers::hint("Use 'xcargo target list --installed' to see installed targets");
helpers::tip("Use 'xcargo target add <triple>' to install a new target");
}
}
TargetAction::Info { target } => {
helpers::section("Target Information");
let target_triple = Target::resolve_alias(&target)?;
match Target::from_triple(&target_triple) {
Ok(target) => {
println!("Triple: {}", target.triple);
println!("Architecture: {}", target.arch);
println!("OS: {}", target.os);
println!("Environment: {}", target.env.as_deref().unwrap_or("default"));
println!("Tier: {:?}", target.tier);
println!();
let requirements = target.get_requirements();
if requirements.linker.is_some() || !requirements.tools.is_empty() || !requirements.system_libs.is_empty() {
helpers::info("Requirements:");
if let Some(linker) = requirements.linker {
println!(" Linker: {}", linker);
}
if !requirements.tools.is_empty() {
println!(" Tools: {}", requirements.tools.join(", "));
}
if !requirements.system_libs.is_empty() {
println!(" System libs: {}", requirements.system_libs.join(", "));
}
println!();
}
let host = Target::detect_host()?;
if target.can_cross_compile_from(&host) {
helpers::success("Can cross-compile from this host");
} else {
helpers::warning("May require container for cross-compilation");
}
println!();
helpers::tip(format!("Add this target: xcargo target add {}", target.triple));
helpers::tip(format!("Build for this target: xcargo build --target {}", target.triple));
}
Err(e) => {
helpers::error(format!("Invalid target: {}", e));
std::process::exit(1);
}
}
}
},
Commands::Init { interactive } => {
if interactive {
run_interactive_setup()?;
} else {
run_basic_setup()?;
}
}
Commands::Config { default } => {
helpers::section("Configuration");
if default {
let config = Config::default();
match config.to_toml() {
Ok(toml) => {
println!("{}", toml);
println!();
helpers::tip("Save this to xcargo.toml to customize your build");
}
Err(e) => {
helpers::error(format!("Failed to generate config: {}", e));
std::process::exit(1);
}
}
} else {
match Config::discover() {
Ok(Some((config, path))) => {
helpers::info(format!("Configuration from: {}", path.display()));
println!();
match config.to_toml() {
Ok(toml) => println!("{}", toml),
Err(e) => {
helpers::error(format!("Failed to serialize config: {}", e));
std::process::exit(1);
}
}
}
Ok(None) => {
helpers::info("No xcargo.toml found, using defaults");
println!();
let config = Config::default();
match config.to_toml() {
Ok(toml) => println!("{}", toml),
Err(e) => {
helpers::error(format!("Failed to generate config: {}", e));
std::process::exit(1);
}
}
println!();
helpers::tip(tips::CONFIG_FILE);
}
Err(e) => {
helpers::error(format!("Failed to load config: {}", e));
std::process::exit(1);
}
}
}
}
Commands::Version => {
println!("xcargo {}", env!("CARGO_PKG_VERSION"));
println!("Cross-compilation, zero friction 🎯");
println!();
println!("https://github.com/ibrahimcesar/xcargo");
}
}
Ok(())
}