asc_bin 2025.5.14

An automated C/C++ package manager, source code scanner, and builder that streamlines dependency management, enhances code analysis, and simplifies the build process.
use tar;
use ureq;
use zstd;

use crate::config;

static CMAKE_NAME: &str = "cmake";
static CMAKE_VERSION: &str = "3.31.5";
static CMAKE_TAG: &str = "cmake3.31.5";
static CMAKE_URL: &str = "https://github.com/ascpkg/asc-downloads/releases/download";

static CMAKE_ZST_SHA1: [(&str, &str); 5] = [
    (
        "cmake-3.31.5-windows-x86_64.tar.zst",
        "27858c730d087790a73ee24e0558c910dc6fe7cf",
    ),
    (
        "cmake-3.31.5-windows-arm64.tar.zst",
        "e101eb3330ac1400a127dcdcb259df98c5a23909",
    ),
    (
        "cmake-3.31.5-macos-universal.tar.zst",
        "7f9cfe68d318340e6134bf94c699e0320623b366",
    ),
    (
        "cmake-3.31.5-linux-x86_64.tar.zst",
        "7d48c9fbf085fbbbfce8bfb255c32466601d47e1",
    ),
    (
        "cmake-3.31.5-linux-aarch64.tar.zst",
        "c859bf689e074bcf403b80b882c03cdd800f4bf8",
    ),
];

pub fn download_cmake_if_not_exists() -> String {
    let name = CMAKE_NAME;
    let version = CMAKE_VERSION;
    let tag = CMAKE_TAG;
    let url_prefix = CMAKE_URL;

    let bin_dir = config::system_paths::DataPath::bin_cmake_dir();
    let (zst_name, url, tar_path, bin_path) = if cfg!(target_os = "windows") {
        let arch = match std::env::consts::ARCH {
            "x86_64" => "x86_64",
            "aarch64" => "arm64",
            name => {
                panic!("unsupported arch {name}");
            }
        };
        let file_name = format!("{name}-{version}-windows-{arch}");
        (
            format!("{file_name}.tar.zst"),
            format!("{url_prefix}/{tag}/{file_name}.tar.zst"),
            format!("{bin_dir}/{file_name}.tar"),
            format!("{bin_dir}/{file_name}/bin/{CMAKE_NAME}.exe"),
        )
    } else if cfg!(target_os = "macos") {
        let arch = match std::env::consts::ARCH {
            "x86_64" => "universal",
            "aarch64" => "universal",
            name => {
                panic!("unsupported arch {name}");
            }
        };
        let file_name = format!("{name}-{version}-macos-{arch}");
        (
            format!("{file_name}.tar.zst"),
            format!("{url_prefix}/{tag}/{file_name}.tar.zst"),
            format!("{bin_dir}/{file_name}.tar"),
            format!("{bin_dir}/{file_name}/bin/{CMAKE_NAME}.app/Contents/bin/cmake"),
        )
    } else {
        let arch = match std::env::consts::ARCH {
            "x86_64" => "x86_64",
            "aarch64" => "aarch64",
            name => {
                panic!("unsupported arch {name}");
            }
        };
        let file_name = format!("{name}-{version}-linux-{arch}");
        (
            format!("{file_name}.tar.zst"),
            format!("{url_prefix}/{tag}/{file_name}.tar.zst"),
            format!("{bin_dir}/{file_name}.tar"),
            format!("{bin_dir}/{file_name}/bin/{CMAKE_NAME}"),
        )
    };
    let zst_path = format!("{tar_path}.tar.zst");

    let info = format!("url: '{url}', tar_path: '{tar_path}'");

    // download if not exists or not file or sha1 mismatch
    let zst_sha1 = std::collections::HashMap::from(CMAKE_ZST_SHA1);
    let meta = std::fs::metadata(&zst_path);
    if meta.is_err()
        || !meta.as_ref().unwrap().is_file()
        || &crate::util::hash::file_sha1_sum(&zst_path).as_str()
            != zst_sha1.get(zst_name.as_str()).unwrap_or(&"")
    {
        for _ in 0..3 {
            tracing::info!(message = "downloading", url = url);

            if meta.as_ref().is_ok() {
                let _ = std::fs::remove_file(&zst_path);
            }

            let agent = ureq::AgentBuilder::new()
                .try_proxy_from_env(true)
                .timeout_read(std::time::Duration::from_secs(15))
                .timeout_write(std::time::Duration::from_secs(5))
                .build();

            let response = agent
                .get(&url)
                .call()
                .expect(&format!("ureq::get error, {info}"));

            let mut zst_file = std::fs::File::create(&zst_path)
                .expect(&format!("std::fs::File::create error, {info}"));
            std::io::copy(&mut response.into_reader(), &mut zst_file)
                .expect(&format!("std::io::copy error, {info}"));

            let calculated_sha1 = crate::util::hash::file_sha1_sum(&zst_path);
            if let Some(expected_sha1) = zst_sha1.get(zst_name.as_str()) {
                if &calculated_sha1.as_str() != expected_sha1 {
                    tracing::error!(
                        message = "sha1 mismatch",
                        expected = expected_sha1,
                        calculated = calculated_sha1
                    );
                    continue;
                }
            }
            break;
        }
    }
    let meta = std::fs::metadata(&zst_path);
    if meta.is_ok() && meta.unwrap().is_file() {
        let calculated_sha1 = crate::util::hash::file_sha1_sum(&zst_path);
        if let Some(expected_sha1) = zst_sha1.get(zst_name.as_str()) {
            if &calculated_sha1.as_str() != expected_sha1 {
                tracing::error!(
                    message = "sha1 mismatch",
                    expected = expected_sha1,
                    calculated = calculated_sha1
                );
            }
        }
    }

    // extract zstd if not exists, not file
    let meta = std::fs::metadata(&tar_path);
    if meta.is_err() || !meta.unwrap().is_file() {
        tracing::info!(message = "extracting", zst = zst_path);

        let zst_file =
            std::fs::File::open(zst_path).expect(&format!("std::fs::File::open error, {info}"));
        let output_file = std::fs::File::create(&tar_path).expect(&format!(
            "std::fs::File::create({:#?}) error, {info}",
            tar_path
        ));
        zstd::stream::copy_decode(zst_file, output_file)
            .expect(&format!("zstd::stream::copy_decode error, {info}"));
    }

    // extract tar if not exists, not file
    let meta = std::fs::metadata(&bin_path);
    if meta.is_err() || !meta.unwrap().is_file() {
        tracing::info!(message = "extracting", tar = tar_path);
        let mut tar = tar::Archive::new(std::fs::File::open(&tar_path).unwrap());
        tar.unpack(&bin_dir)
            .expect(&format!("tar::Archive::unpack error, {info}"));
    }

    return bin_path;
}