zv 0.12.0

Ziglang Version Manager and Project Starter
use crate::app::network::zls as zls_api;
use crate::app::zls_download::download_zls_prebuilt;
use crate::app::zls_source::build_zls_from_source;
use crate::{App, Shim, suggest};
use color_eyre::eyre::eyre;
use std::path::Path;

fn active_zig_semver_string(active: &crate::ZigVersion) -> Option<String> {
    active.version().map(|v| v.to_string())
}

pub(crate) async fn provision_zls(
    app: &mut App,
    download: bool,
    force: bool,
    update: bool,
) -> crate::Result<()> {
    if !app.is_initialized() {
        crate::tools::error(
            "zv is not initialized. Run 'zv sync' first to set up directories and the zv binary.",
        );
        std::process::exit(1);
    }

    let active_zig = app.get_active_version().ok_or_else(|| {
        crate::tools::error("No active Zig version found");
        suggest!("Set one with {}", cmd = "zv use <version>");
        eyre!("No active Zig version")
    })?;

    let active_zig_exe = app
        .toolchain_manager
        .get_active_install()
        .map(|zi| zi.path.join(Shim::Zig.executable_name()))
        .ok_or_else(|| eyre!("No active Zig installation path found"))?;

    provision_zls_for(
        app,
        &active_zig,
        &active_zig_exe,
        download,
        force,
        update,
        true,
    )
    .await
}

pub(crate) async fn provision_zls_for(
    app: &mut App,
    zig_version: &crate::ZigVersion,
    zig_exe: &Path,
    download: bool,
    force: bool,
    update: bool,
    ensure_shim: bool,
) -> crate::Result<()> {
    let zig_version_string = active_zig_semver_string(zig_version).ok_or_else(|| {
        eyre!(
            "Unable to determine active Zig semantic version for '{}'.",
            zig_version
        )
    })?;

    if !force
        && !update
        && let Some((zls_version, zls_path)) = app.get_zls_for_zig(zig_version)
    {
        if ensure_shim && let Some(active_install) = app.toolchain_manager.get_active_install() {
            app.toolchain_manager
                .deploy_shims(active_install, false, true)
                .await?;
        }
        println!(
            "Compatible ZLS already provisioned: {} ({})",
            zls_version,
            zls_path.display()
        );
        return Ok(());
    }

    let release = zls_api::select_version(&zig_version_string)
        .await
        .map_err(|e| {
            eyre!(
                "Failed to query compatible ZLS for Zig '{}': {}",
                zig_version_string,
                e
            )
        })?;
    let host_target = crate::app::utils::host_target()
        .ok_or_else(|| eyre!("Could not determine host target for current machine"))?;

    let zls_dest_dir = app.paths.zls_dir().join(&release.version);
    let zls_binary = if download {
        download_zls_prebuilt(app, &release, &host_target, &zls_dest_dir).await?
    } else {
        let zls_src_dir = app.paths.zls_src_dir();
        build_zls_from_source(&release.version, zig_exe, &zls_src_dir, &zls_dest_dir).await?
    };

    app.record_zls_mapping(zig_version, &release.version)?;

    if ensure_shim && let Some(active_install) = app.toolchain_manager.get_active_install() {
        app.toolchain_manager
            .deploy_shims(active_install, false, true)
            .await?;
    }

    println!(
        "Provisioned ZLS {} (release date {}) for Zig {} at {}",
        release.version,
        release.date,
        zig_version_string,
        zls_binary.display()
    );

    Ok(())
}