use std::io::Write as _;
use std::process::Command;
use yog_ids::platform::{self, Platform};
use super::targets::{
RUST_TOOLCHAIN, first_line_of, has_zigbuild, install_targets, installed_targets,
};
pub(super) fn setup() -> Result<(), String> {
eprintln!("==> yog setup — checking build dependencies\n");
let rust_ok = check_rust();
let zig_build_ok = if rust_ok { check_zigbuild() } else { false };
let _zig_ok = check_zig();
if rust_ok && zig_build_ok {
check_targets();
}
eprintln!();
if rust_ok && zig_build_ok {
eprintln!("==> all good — `yog build` should produce all 5 platforms.");
} else if rust_ok {
eprintln!(
"==> Rust OK but cross-compilation incomplete. Fix the above, then re-run `yog setup`."
);
} else {
eprintln!("==> Install Rust first, then re-run `yog setup`.");
}
Ok(())
}
pub(super) fn check_rust() -> bool {
eprint!(" [?] Rust / cargo ... ");
if let Some(ver) = first_line_of("cargo", &["--version"]) {
eprintln!("OK ({ver})");
true
} else {
eprintln!("NOT FOUND");
eprintln!(" Install Rust via rustup:");
eprintln!(" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
false
}
}
pub(super) fn check_zigbuild() -> bool {
eprint!(" [?] cargo-zigbuild ... ");
if has_zigbuild() {
eprintln!("OK");
true
} else {
eprintln!("NOT FOUND");
eprint!(" Install cargo-zigbuild? [Y/n]: ");
std::io::stdout().flush().ok();
let mut input = String::new();
std::io::stdin().read_line(&mut input).ok();
let input = input.trim().to_lowercase();
if input.is_empty() || input == "y" {
eprintln!(" Running: cargo install cargo-zigbuild");
let status = Command::new("cargo")
.args(["install", "cargo-zigbuild"])
.status();
match status {
Ok(s) if s.success() => {
eprintln!(" cargo-zigbuild installed.");
return true;
}
_ => eprintln!(
" Installation failed. Install manually: cargo install cargo-zigbuild"
),
}
} else {
eprintln!(" Skipped. Cross-compilation will only work for the host platform.");
}
false
}
}
pub(super) fn check_zig() -> bool {
eprint!(" [?] zig ... ");
if let Some(ver) = first_line_of("zig", &["version"]) {
eprintln!("OK ({ver})");
true
} else {
eprintln!("NOT FOUND");
eprint!(" Install zig? [Y/n]: ");
std::io::stdout().flush().ok();
let mut input = String::new();
std::io::stdin().read_line(&mut input).ok();
let input = input.trim().to_lowercase();
if (input.is_empty() || input == "y") && try_install_zig() {
return true;
}
eprintln!(" Download zig from: https://ziglang.org/download/");
eprintln!(" Extract, add to PATH, then re-run `yog setup`.");
false
}
}
pub(super) fn try_install_zig() -> bool {
if Command::new("snap")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
{
eprintln!(" Running: snap install zig --classic --beta");
if Command::new("snap")
.args(["install", "zig", "--classic", "--beta"])
.status()
.map(|s| s.success())
.unwrap_or(false)
{
eprintln!(" zig installed via snap.");
return true;
}
}
if Command::new("brew")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
{
eprintln!(" Running: brew install zig");
if Command::new("brew")
.args(["install", "zig"])
.status()
.map(|s| s.success())
.unwrap_or(false)
{
eprintln!(" zig installed via brew.");
return true;
}
}
eprintln!(" Could not auto-install zig (no snap or brew found).");
false
}
pub(super) fn check_targets() {
eprintln!(" [?] rustup cross-compile targets ...");
let installed = installed_targets();
let needed: Vec<&str> = platform::ALL
.iter()
.map(|t| t.triple)
.filter(|triple| !installed.iter().any(|s| s == triple))
.collect();
if needed.is_empty() {
eprintln!(" all 5 targets installed.");
return;
}
eprintln!(
" installing Rust {RUST_TOOLCHAIN}: {} ...",
needed.join(", ")
);
let all: Vec<&Platform> = platform::ALL.iter().collect();
install_targets(&all);
let installed = installed_targets();
let missing = needed
.iter()
.filter(|triple| !installed.iter().any(|s| s == *triple))
.count();
eprintln!(" {}", if missing == 0 { "done" } else { "FAILED" });
}
pub(super) struct DoctorReport {
pub(super) cli_version: &'static str,
pub(super) rust_toolchain: &'static str,
pub(super) cargo: Option<String>,
pub(super) rustup: Option<String>,
pub(super) cargo_zigbuild: bool,
pub(super) zig: Option<String>,
pub(super) targets: Vec<(&'static str, &'static str, bool)>,
}
pub(super) fn doctor() -> DoctorReport {
let installed = installed_targets();
DoctorReport {
cli_version: env!("CARGO_PKG_VERSION"),
rust_toolchain: RUST_TOOLCHAIN,
cargo: first_line_of("cargo", &["--version"]),
rustup: first_line_of("rustup", &["--version"]),
cargo_zigbuild: has_zigbuild(),
zig: first_line_of("zig", &["version"]),
targets: platform::ALL
.iter()
.map(|t| (t.tag, t.triple, installed.iter().any(|s| s == t.triple)))
.collect(),
}
}
impl DoctorReport {
fn print(&self) {
let show = |name: &str, v: &Option<String>| match v {
Some(v) => println!(" [ok] {name:<16} {v}"),
None => println!(" [--] {name:<16} not found"),
};
println!(
"yog {} (mods build with Rust {})",
self.cli_version, self.rust_toolchain
);
show("cargo", &self.cargo);
show("rustup", &self.rustup);
println!(
" [{}] {:<16} {}",
if self.cargo_zigbuild { "ok" } else { "--" },
"cargo-zigbuild",
if self.cargo_zigbuild {
"installed"
} else {
"not found"
}
);
show("zig", &self.zig);
for (tag, triple, installed) in &self.targets {
println!(
" [{}] {tag:<16} {triple}",
if *installed { "ok" } else { "--" }
);
}
println!("Run `yog setup` to install what's missing.");
}
}
pub(super) fn doctor_cmd() {
doctor().print();
}