use crate::config::Config;
use crate::error::{Error, Result};
use crate::output::{helpers, tips};
use crate::target::Target;
use crate::toolchain::ToolchainManager;
use std::process::Command;
#[derive(Debug, Clone)]
pub struct BuildOptions {
pub target: Option<String>,
pub release: bool,
pub cargo_args: Vec<String>,
pub toolchain: Option<String>,
pub verbose: bool,
}
impl Default for BuildOptions {
fn default() -> Self {
Self {
target: None,
release: false,
cargo_args: Vec::new(),
toolchain: None,
verbose: false,
}
}
}
pub struct Builder {
toolchain_manager: ToolchainManager,
config: Config,
}
impl Builder {
pub fn new() -> Result<Self> {
let toolchain_manager = ToolchainManager::new()?;
let config = Config::discover()?.map(|(c, _)| c).unwrap_or_default();
Ok(Self {
toolchain_manager,
config,
})
}
pub fn with_config(config: Config) -> Result<Self> {
let toolchain_manager = ToolchainManager::new()?;
Ok(Self {
toolchain_manager,
config,
})
}
pub fn build(&self, options: &BuildOptions) -> Result<()> {
helpers::section("xcargo build");
let target_triple = if let Some(target) = &options.target {
target.clone()
} else if let Some(default_target) = self.config.targets.default.first() {
helpers::info(format!("Using default target from config: {}", default_target));
default_target.clone()
} else {
let host = Target::detect_host()?;
helpers::info(format!("No target specified, using host: {}", host.triple));
host.triple
};
let target = Target::from_triple(&target_triple)?;
helpers::progress(format!("Building for target: {}", target.triple));
let toolchain = if let Some(tc) = &options.toolchain {
tc.clone()
} else {
"stable".to_string()
};
helpers::progress(format!("Checking toolchain and target..."));
self.toolchain_manager.prepare_target(&toolchain, &target)?;
helpers::success("Toolchain and target ready");
if target.os != Target::detect_host()?.os {
helpers::tip("Cross-compiling to a different OS");
if self.config.container.use_when == "target.os != host.os" {
helpers::hint("Container builds not yet implemented - using native toolchain");
}
}
let requirements = target.get_requirements();
if let Some(linker) = requirements.linker {
helpers::hint(format!("Recommended linker: {}", linker));
helpers::tip(format!("Set linker in xcargo.toml: [targets.\"{}\"] linker = \"{}\"",
target.triple, linker));
}
helpers::progress("Running cargo build...");
let mut cmd = Command::new("cargo");
if options.toolchain.is_some() {
cmd.arg(format!("+{}", toolchain));
}
cmd.arg("build");
cmd.arg("--target").arg(&target.triple);
if options.release {
cmd.arg("--release");
}
if options.verbose || self.config.build.cargo_flags.contains(&"--verbose".to_string()) {
cmd.arg("--verbose");
}
for flag in &self.config.build.cargo_flags {
if flag != "--verbose" || !options.verbose {
cmd.arg(flag);
}
}
for arg in &options.cargo_args {
cmd.arg(arg);
}
if options.verbose {
helpers::info(format!("Executing: {:?}", cmd));
}
let status = cmd.status()
.map_err(|e| Error::Build(format!("Failed to execute cargo: {}", e)))?;
if status.success() {
println!(); helpers::success(format!("Build completed for {}", target.triple));
if options.release {
helpers::tip(format!("Release build artifacts are in target/{}/release/", target.triple));
} else {
helpers::tip(format!("Debug build artifacts are in target/{}/debug/", target.triple));
}
if target.os == "windows" && Target::detect_host()?.os != "windows" {
helpers::tip(format!("Test Windows binaries with Wine: wine target/{}/debug/your-app.exe", target.triple));
}
Ok(())
} else {
Err(Error::Build(format!(
"Build failed for target {}",
target.triple
)))
}
}
pub fn build_all(&self, targets: &[String], options: &BuildOptions) -> Result<()> {
helpers::section("xcargo build (multiple targets)");
helpers::info(format!("Building for {} targets", targets.len()));
let mut successes = Vec::new();
let mut failures = Vec::new();
for (idx, target) in targets.iter().enumerate() {
println!("\n[{}/{}] Target: {}", idx + 1, targets.len(), target);
println!("{}", "─".repeat(50));
let mut target_options = options.clone();
target_options.target = Some(target.clone());
match self.build(&target_options) {
Ok(()) => successes.push(target.clone()),
Err(e) => {
helpers::error(format!("Failed to build {}: {}", target, e));
failures.push(target.clone());
}
}
}
println!("\n");
helpers::section("Build Summary");
helpers::success(format!("{} target(s) built successfully", successes.len()));
if !failures.is_empty() {
helpers::error(format!("{} target(s) failed", failures.len()));
for target in &failures {
helpers::error(format!(" - {}", target));
}
return Err(Error::Build("Some targets failed to build".to_string()));
}
helpers::tip(tips::PARALLEL_BUILDS);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_options_default() {
let options = BuildOptions::default();
assert!(options.target.is_none());
assert!(!options.release);
assert!(options.cargo_args.is_empty());
}
#[test]
fn test_builder_new() {
let builder = Builder::new();
if builder.is_err() {
return;
}
assert!(builder.is_ok());
}
}