use std::collections::HashMap;
use crate::{
ResolvedZigVersion, ZigVersion, ZvError,
app::{App, Either},
cli::r#use::resolve_zig_version,
};
use color_eyre::eyre::{Context, Result, eyre};
use yansi::Paint;
pub(crate) async fn install_versions(
zig_versions: Vec<ZigVersion>,
app: &mut App,
force_ziglang: bool,
) -> Result<()> {
if zig_versions.is_empty() {
return Err(eyre!(
"At least one version must be specified. e.g., 'zv install latest' or 'zv install 0.15.1,master'"
));
}
let is_single_version = zig_versions.len() == 1;
let should_set_active = is_single_version && app.toolchain_manager.installations_empty();
if should_set_active {
println!(
"📦 Installing {} (will be set as active zig)...",
Paint::blue(&zig_versions[0].to_string())
);
} else if is_single_version {
println!(
"📦 Installing {}...",
Paint::blue(&zig_versions[0].to_string())
);
}
let zig_versions = crate::tools::deduplicate_semver_variants(zig_versions);
let mut resolved_map: HashMap<ResolvedZigVersion, Either> = HashMap::new();
let mut resolution_errors: Vec<(ZigVersion, ZvError)> = Vec::new();
for zig_version in zig_versions {
match resolve_zig_version(app, &zig_version).await {
Ok(resolved) => {
let install_either = app.to_install.take().ok_or_else(|| {
resolution_errors.push((
zig_version,
ZvError::ZigVersionResolveError(eyre!(
"Internal Error: App failed to set to_install for zig version.",
)),
));
});
if install_either.is_err() {
continue;
}
resolved_map
.entry(resolved)
.or_insert(install_either.unwrap());
}
Err(e) => {
let error_msg = match e {
ZvError::ZigVersionResolveError(err) => ZvError::ZigVersionResolveError(eyre!(
"Failed to resolve version '{}': {}. Try running 'zv sync' to update the index or 'zv list' to see available versions.",
zig_version,
err
)),
_ => e,
};
eprintln!(
"❌ Failed to resolve {}: {}",
Paint::red(&zig_version.to_string()),
error_msg
);
resolution_errors.push((zig_version, error_msg));
}
}
}
if resolved_map.is_empty() {
return Err(eyre!("Failed to resolve any versions"));
}
let mut installed_versions = Vec::new();
let mut failed_versions = Vec::new();
println!(
"📦 Installing {} version(s)...",
Paint::blue(&resolved_map.keys().len().to_string())
);
for (resolved_version, install_either) in resolved_map {
match install_resolved_version(
&resolved_version,
install_either,
app,
force_ziglang,
should_set_active,
)
.await
{
Ok(()) => {
installed_versions.push(resolved_version);
}
Err(e) => {
eprintln!(
"❌ Failed to install {}: {}",
Paint::red(&resolved_version.to_string()),
e
);
failed_versions.push((resolved_version, e));
}
}
}
if !installed_versions.is_empty() {
println!();
for resolved in &installed_versions {
if should_set_active {
println!(
"✅ Installed and activated: {}",
Paint::green(&resolved.version().to_string())
);
} else {
println!(
"✅ Installed: {}",
Paint::green(&resolved.version().to_string())
);
}
}
}
if !failed_versions.is_empty() {
println!();
eprintln!("❌ Failed installations:");
for (version, _) in &failed_versions {
eprintln!(" • {}", Paint::red(&version.to_string()));
}
}
if installed_versions.is_empty() {
return Err(eyre!("All version installations failed"));
}
Ok(())
}
async fn install_resolved_version(
resolved_version: &ResolvedZigVersion,
install_either: Either,
app: &mut App,
force_ziglang: bool,
set_active: bool,
) -> Result<()> {
if let Some(p) = app.check_installed(resolved_version) {
if set_active {
app.set_active_version(resolved_version, Some(p)).await?;
}
return Ok(());
}
app.to_install = Some(install_either.clone());
match install_either {
Either::Release(_) => {
app.install_release(force_ziglang).await.wrap_err_with(|| {
format!(
"Failed to download and install Zig version {}",
resolved_version
)
})?;
}
Either::Version(_) => {
app.install_direct(force_ziglang)
.await
.wrap_err_with(|| format!("Failed to install Zig version {}", resolved_version))?;
}
}
if set_active {
app.set_active_version(resolved_version, None).await?;
}
Ok(())
}