arm_toolchain/cli/
install.rs1use 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#[derive(Debug, clap::Parser)]
22pub struct InstallArgs {
23 pub version: Option<ToolchainVersion>,
25 #[clap(long, short)]
27 pub force: bool,
28}
29
30pub async fn install(args: InstallArgs) -> Result<(), CliError> {
32 let client = ToolchainClient::using_data_dir().await?;
33
34 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}