Skip to main content

o_/
toolchain.rs

1use std::env::consts::{ARCH, OS};
2use std::fs::File;
3use std::io::{BufReader, copy};
4use std::path::PathBuf;
5
6pub fn install(
7    provider: &str,
8    user: &str,
9    repo: &str,
10) -> Result<PathBuf, Box<dyn std::error::Error>> {
11    let current_os = match OS {
12        "windows" => "windows",
13        "macos" => "darwin",
14        "linux" => "linux",
15        other => other,
16    };
17
18    let current_arch = match ARCH {
19        "x86_64" => "amd64",
20        "aarch64" => "arm64",
21        other => other,
22    };
23
24    let extension = if OS == "windows" { "zip" } else { "tar.gz" };
25
26    let url = format!(
27        "https://{provider}/{user}/{repo}/releases/latest/download/{repo}-{current_os}-{current_arch}.{extension}"
28    );
29
30    println!("Detected Target: OS={}, ARCH={}", OS, ARCH);
31    println!("Downloading release from: {}", url);
32
33    let client = reqwest::blocking::Client::new();
34    let response = client.get(&url).send()?;
35
36    if !response.status().is_success() {
37        return Err(format!("Server returned an error status: {}", response.status()).into());
38    }
39
40    let target_dir = tempfile::tempdir()?.keep();
41    let target_filename = format!("{repo}-{current_os}-{current_arch}.{extension}");
42    let archive_path = target_dir.join(&target_filename);
43
44    let mut archive_file = File::create(&archive_path)?;
45    let mut content = response;
46    let bytes = copy(&mut content, &mut archive_file)?;
47    println!(
48        "Successfully downloaded {} bytes to {:?}",
49        bytes, archive_path
50    );
51
52    let file = File::open(&archive_path)?;
53    let reader = BufReader::new(file);
54
55    if extension == "zip" {
56        println!("Extracting ZIP archive...");
57        let mut archive = zip::ZipArchive::new(reader)?;
58
59        for i in 0..archive.len() {
60            let mut file = archive.by_index(i)?;
61            let outpath = match file.enclosed_name() {
62                Some(path) => target_dir.join(path),
63                None => continue,
64            };
65
66            if file.name().ends_with('/') {
67                std::fs::create_dir_all(&outpath)?;
68            } else {
69                if let Some(p) = outpath.parent()
70                    && !p.exists()
71                {
72                    std::fs::create_dir_all(p)?;
73                }
74                let mut outfile = File::create(&outpath)?;
75                copy(&mut file, &mut outfile)?;
76            }
77
78            #[cfg(unix)]
79            {
80                use std::os::unix::fs::PermissionsExt;
81                if let Some(mode) = file.unix_mode() {
82                    std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
83                }
84            }
85        }
86    } else if extension == "tar.gz" {
87        println!("Extracting TAR.GZ archive...");
88        let tar_gz = flate2::read::GzDecoder::new(reader);
89        let mut archive = tar::Archive::new(tar_gz);
90        archive.unpack(&target_dir)?;
91    }
92
93    if let Err(e) = std::fs::remove_file(&archive_path) {
94        eprintln!("Warning: Failed to remove temporary archive file: {}", e);
95    }
96
97    println!("Extraction completed. Files stored in: {:?}", target_dir);
98
99    Ok(target_dir)
100}