arm_toolchain/cli/
install.rs

1use std::{sync::Arc, time::Duration};
2
3use indicatif::{MultiProgress, ProgressBar};
4use inquire::Confirm;
5use owo_colors::OwoColorize;
6use tokio::task::spawn_blocking;
7use tokio_util::sync::CancellationToken;
8
9use crate::{
10    cli::{
11        CliError, PROGRESS_STYLE_DL, PROGRESS_STYLE_EXTRACT, PROGRESS_STYLE_EXTRACT_SPINNER,
12        PROGRESS_STYLE_VERIFY, ctrl_c_cancel, msg,
13    },
14    toolchain::{
15        HostArch, HostOS, InstallState, ToolchainClient, ToolchainError, ToolchainRelease,
16        ToolchainVersion,
17    },
18};
19
20/// Configuration for [`install`].
21#[derive(Debug, clap::Parser)]
22pub struct InstallArgs {
23    /// Version of the toolchain to install
24    pub version: Option<ToolchainVersion>,
25    /// Skip install if toolchain is up-to-date.
26    #[clap(long, short)]
27    pub force: bool,
28}
29
30/// Remove a toolchain to the system.
31pub async fn install(args: InstallArgs) -> Result<(), CliError> {
32    let client = ToolchainClient::using_data_dir().await?;
33
34    // If "latest" specified we have to figure out what that actually means first
35    let toolchain_release;
36    let toolchain_version;
37    let install_latest;
38
39    if let Some(version) = args.version
40        && version.name != "latest"
41    {
42        install_latest = false;
43        toolchain_version = version;
44        toolchain_release = client.get_release(&toolchain_version).await?;
45    } else {
46        install_latest = true;
47        toolchain_release = client.latest_release().await?;
48        toolchain_version = toolchain_release.version().to_owned();
49    }
50
51    if !args.force {
52        let already_installed = client.install_path_for(&toolchain_version);
53        if already_installed.exists() {
54            println!(
55                "Toolchain already installed: {} at {}",
56                toolchain_version.to_string().bold(),
57                already_installed.display().green()
58            );
59
60            if client.active_toolchain().as_ref() == Some(&toolchain_version) {
61                println!(
62                    "(Enable it with the `use {}` subcommand)",
63                    if install_latest {
64                        "latest".to_string()
65                    } else {
66                        toolchain_version.to_string()
67                    }
68                );
69            }
70
71            return Ok(());
72        }
73    }
74
75    confirm_install(&toolchain_version, install_latest).await?;
76
77    let old_version = client.active_toolchain();
78
79    let token = ctrl_c_cancel();
80    install_with_progress_bar(&client, &toolchain_release, token.clone()).await?;
81
82    if old_version.is_none() {
83        msg!("Activated", "{toolchain_version}");
84    }
85
86    token.cancel();
87    Ok(())
88}
89
90pub async fn confirm_install(version: &ToolchainVersion, latest: bool) -> Result<(), CliError> {
91    let confirm_message = format!(
92        "Download & install {}ARM toolchain {version}?",
93        if latest { "latest " } else { "" },
94    );
95
96    let confirmation = spawn_blocking(move || {
97        Confirm::new(&confirm_message)
98            .with_default(true)
99            .with_help_message("Required support libraries for building C/C++ code. No = cancel")
100            .prompt()
101    })
102    .await
103    .unwrap()?;
104
105    if !confirmation {
106        eprintln!("Cancelled.");
107        return Err(ToolchainError::Cancelled)?;
108    }
109
110    Ok(())
111}
112
113pub async fn install_with_progress_bar(
114    client: &ToolchainClient,
115    release: &ToolchainRelease,
116    cancel_token: CancellationToken,
117) -> Result<(), CliError> {
118    let asset = release.asset_for(HostOS::current(), HostArch::current())?;
119
120    msg!("Downloading", "{}", asset.name,);
121
122    let multi_bar = MultiProgress::new();
123    let download_bar = ProgressBar::no_length().with_style(PROGRESS_STYLE_DL.clone());
124    multi_bar.add(download_bar.clone());
125
126    let verify_bar = ProgressBar::no_length()
127        .with_style(PROGRESS_STYLE_VERIFY.clone())
128        .with_message("Verifying");
129    multi_bar.add(verify_bar.clone());
130
131    let extract_bar = ProgressBar::no_length()
132        .with_message("Extracting toolchain")
133        .with_style(PROGRESS_STYLE_EXTRACT_SPINNER.clone());
134    multi_bar.add(extract_bar.clone());
135
136    let progress_handler = Arc::new(move |update| match update {
137        InstallState::DownloadBegin {
138            asset_size,
139            bytes_read,
140        } => {
141            download_bar.reset();
142            download_bar.enable_steady_tick(Duration::from_millis(300));
143            download_bar.set_length(asset_size);
144            download_bar.set_position(bytes_read);
145            download_bar.reset_eta();
146        }
147        InstallState::Download { bytes_read } => {
148            download_bar.set_position(bytes_read);
149        }
150        InstallState::DownloadFinish => {
151            download_bar.disable_steady_tick();
152            download_bar.finish_with_message("Download complete");
153        }
154        InstallState::VerifyingBegin { asset_size } => {
155            verify_bar.reset();
156            verify_bar.set_length(asset_size);
157        }
158        InstallState::Verifying { bytes_read } => {
159            verify_bar.set_position(bytes_read);
160        }
161        InstallState::VerifyingFinish => {
162            verify_bar.finish_with_message("Verification complete");
163        }
164        InstallState::ExtractBegin => {
165            extract_bar.set_style(PROGRESS_STYLE_EXTRACT_SPINNER.clone());
166            extract_bar.enable_steady_tick(Duration::from_millis(300));
167        }
168        InstallState::ExtractCopy {
169            bytes_copied,
170            total_size,
171        } => {
172            if extract_bar.length().is_none() {
173                extract_bar.set_style(PROGRESS_STYLE_EXTRACT.clone());
174                extract_bar.reset();
175            }
176
177            extract_bar.set_length(total_size);
178            extract_bar.set_position(bytes_copied);
179        }
180        InstallState::ExtractCleanUp => {}
181        InstallState::ExtractDone => {
182            extract_bar.finish_with_message("Extraction complete");
183        }
184    });
185
186    let destination = client
187        .download_and_install(release, asset, progress_handler, cancel_token)
188        .await?;
189
190    msg!("Downloaded", "to {}", destination.display());
191
192    Ok(())
193}