Skip to content

Commit 8c247a4

Browse files
committed
Decouple install toolchain handling, add error types for it
1 parent c9f3000 commit 8c247a4

File tree

3 files changed

+167
-63
lines changed

3 files changed

+167
-63
lines changed

crates/cargo-gpu-cache/src/install_toolchain.rs

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,35 @@
11
//! toolchain installation logic
22
3+
use std::process::{Command, Stdio};
4+
35
use anyhow::Context as _;
46
use crossterm::tty::IsTty as _;
7+
use rustc_codegen_spirv_cache::toolchain::is_toolchain_installed;
58

6-
/// Use `rustup` to install the toolchain and components, if not already installed.
7-
///
8-
/// Pretty much runs:
9-
///
10-
/// * rustup toolchain add nightly-2024-04-24
11-
/// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools
12-
pub fn ensure_toolchain_and_components_exist(
13-
channel: &str,
14-
skip_toolchain_install_consent: bool,
15-
) -> anyhow::Result<()> {
16-
// Check for the required toolchain
17-
let output_toolchain_list = std::process::Command::new("rustup")
18-
.args(["toolchain", "list"])
9+
use crate::user_output;
10+
11+
/// Installs the given toolchain using `rustup`.
12+
pub fn install_toolchain(channel: &str) -> anyhow::Result<()> {
13+
let output_toolchain_add = Command::new("rustup")
14+
.args(["toolchain", "add"])
15+
.arg(channel)
16+
.stdout(Stdio::inherit())
17+
.stderr(Stdio::inherit())
1918
.output()
20-
.context("running rustup command")?;
19+
.context("adding toolchain")?;
2120
anyhow::ensure!(
22-
output_toolchain_list.status.success(),
23-
"could not list installed toolchains"
21+
output_toolchain_add.status.success(),
22+
"could not install required toolchain"
2423
);
25-
let string_toolchain_list = String::from_utf8_lossy(&output_toolchain_list.stdout);
26-
if string_toolchain_list
27-
.split_whitespace()
28-
.any(|toolchain| toolchain.starts_with(channel))
29-
{
30-
log::debug!("toolchain {channel} is already installed");
31-
} else {
32-
let message = format!("Rust {channel} with `rustup`");
33-
get_consent_for_toolchain_install(
34-
format!("Install {message}").as_ref(),
35-
skip_toolchain_install_consent,
36-
)?;
37-
crate::user_output!("Installing {message}\n")?;
38-
39-
let output_toolchain_add = std::process::Command::new("rustup")
40-
.args(["toolchain", "add"])
41-
.arg(channel)
42-
.stdout(std::process::Stdio::inherit())
43-
.stderr(std::process::Stdio::inherit())
44-
.output()
45-
.context("adding toolchain")?;
46-
anyhow::ensure!(
47-
output_toolchain_add.status.success(),
48-
"could not install required toolchain"
49-
);
50-
}
24+
Ok(())
25+
}
5126

52-
// Check for the required components
53-
let output_component_list = std::process::Command::new("rustup")
27+
/// Components which are required to be installed for a toolchain to be usable with `rust-gpu`.
28+
pub const REQUIRED_TOOLCHAIN_COMPONENTS: [&str; 3] = ["rust-src", "rustc-dev", "llvm-tools"];
29+
30+
/// Checks if all the required components of the given toolchain are installed using `rustup`.
31+
pub fn all_required_toolchain_components_installed(channel: &str) -> anyhow::Result<bool> {
32+
let output_component_list = Command::new("rustup")
5433
.args(["component", "list", "--toolchain"])
5534
.arg(channel)
5635
.output()
@@ -59,38 +38,72 @@ pub fn ensure_toolchain_and_components_exist(
5938
output_component_list.status.success(),
6039
"could not list installed components"
6140
);
41+
6242
let string_component_list = String::from_utf8_lossy(&output_component_list.stdout);
63-
let required_components = ["rust-src", "rustc-dev", "llvm-tools"];
6443
let installed_components = string_component_list.lines().collect::<Vec<_>>();
65-
let all_components_installed = required_components.iter().all(|component| {
44+
let all_components_installed = REQUIRED_TOOLCHAIN_COMPONENTS.iter().all(|component| {
6645
installed_components.iter().any(|installed_component| {
6746
let is_component = installed_component.starts_with(component);
6847
let is_installed = installed_component.ends_with("(installed)");
6948
is_component && is_installed
7049
})
7150
});
72-
if all_components_installed {
73-
log::debug!("all required components are installed");
51+
Ok(all_components_installed)
52+
}
53+
54+
/// Installs all the required components for the given toolchain using `rustup`.
55+
pub fn install_required_toolchain_components(channel: &str) -> anyhow::Result<()> {
56+
let output_component_add = Command::new("rustup")
57+
.args(["component", "add", "--toolchain"])
58+
.arg(channel)
59+
.args(REQUIRED_TOOLCHAIN_COMPONENTS)
60+
.stdout(Stdio::inherit())
61+
.stderr(Stdio::inherit())
62+
.output()
63+
.context("adding rustup component")?;
64+
anyhow::ensure!(
65+
output_component_add.status.success(),
66+
"could not install required components"
67+
);
68+
Ok(())
69+
}
70+
71+
/// Use `rustup` to install the toolchain and components, if not already installed.
72+
///
73+
/// Pretty much runs:
74+
///
75+
/// * rustup toolchain add nightly-2024-04-24
76+
/// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools
77+
pub fn ensure_toolchain_and_components_exist(
78+
channel: &str,
79+
skip_toolchain_install_consent: bool,
80+
) -> anyhow::Result<()> {
81+
// Check for the required toolchain
82+
if is_toolchain_installed(channel)? {
83+
log::debug!("toolchain {channel} is already installed");
7484
} else {
75-
let message = "toolchain components [rust-src, rustc-dev, llvm-tools] with `rustup`";
85+
let message = format!("Rust {channel} with `rustup`");
7686
get_consent_for_toolchain_install(
7787
format!("Install {message}").as_ref(),
7888
skip_toolchain_install_consent,
7989
)?;
80-
crate::user_output!("Installing {message}\n")?;
81-
82-
let output_component_add = std::process::Command::new("rustup")
83-
.args(["component", "add", "--toolchain"])
84-
.arg(channel)
85-
.args(["rust-src", "rustc-dev", "llvm-tools"])
86-
.stdout(std::process::Stdio::inherit())
87-
.stderr(std::process::Stdio::inherit())
88-
.output()
89-
.context("adding rustup component")?;
90-
anyhow::ensure!(
91-
output_component_add.status.success(),
92-
"could not install required components"
90+
user_output!("Installing {message}\n")?;
91+
install_toolchain(channel)?;
92+
}
93+
94+
// Check for the required components
95+
if all_required_toolchain_components_installed(channel)? {
96+
log::debug!("all required components of toolchain {channel} are installed");
97+
} else {
98+
let message = format!(
99+
"components {REQUIRED_TOOLCHAIN_COMPONENTS:?} for toolchain {channel} with `rustup`"
93100
);
101+
get_consent_for_toolchain_install(
102+
format!("Install {message}").as_ref(),
103+
skip_toolchain_install_consent,
104+
)?;
105+
user_output!("Installing {message}\n")?;
106+
install_required_toolchain_components(channel)?;
94107
}
95108

96109
Ok(())
@@ -112,7 +125,7 @@ fn get_consent_for_toolchain_install(
112125

113126
log::debug!("asking for consent to install the required toolchain");
114127
crossterm::terminal::enable_raw_mode().context("enabling raw mode")?;
115-
crate::user_output!("{prompt} [y/n]: ")?;
128+
user_output!("{prompt} [y/n]: ")?;
116129
let mut input = crossterm::event::read().context("reading crossterm event")?;
117130

118131
if let crossterm::event::Event::Key(crossterm::event::KeyEvent {

crates/rustc_codegen_spirv-cache/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ pub mod cache;
2424
pub mod metadata;
2525
pub mod spirv_source;
2626
pub mod target_specs;
27+
pub mod toolchain;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! This module deals with an installation of Rust toolchain required by `rust-gpu`
2+
//! (and its components).
3+
4+
use std::{
5+
io,
6+
process::{Command, Output},
7+
};
8+
9+
/// Checks if the given toolchain is installed using `rustup`.
10+
///
11+
/// # Errors
12+
///
13+
/// Returns an error if any error occurs while using `rustup`.
14+
#[inline]
15+
pub fn is_toolchain_installed(channel: &str) -> Result<bool, RustupCallError> {
16+
let mut command = Command::new("rustup");
17+
command.args(["toolchain", "list"]);
18+
19+
let output = match command.output() {
20+
Ok(output) => output,
21+
Err(source) => return Err(RustupCallError::io(command, source)),
22+
};
23+
if !output.status.success() {
24+
return Err(RustupCallError::command_fail(command, output));
25+
}
26+
27+
let toolchain_list = String::from_utf8_lossy(&output.stdout);
28+
let installed = toolchain_list
29+
.split_whitespace()
30+
.any(|toolchain| toolchain.starts_with(channel));
31+
Ok(installed)
32+
}
33+
34+
/// An error indicating failure while using `rustup`.
35+
#[derive(Debug, thiserror::Error)]
36+
#[non_exhaustive]
37+
pub enum RustupCallError {
38+
/// IO error occurred while calling some `rustup` command.
39+
#[error("IO error occurred while calling `{command:?}`: {source}")]
40+
Io {
41+
/// The command which was called.
42+
command: Box<Command>,
43+
/// Source of the error.
44+
source: io::Error,
45+
},
46+
/// Result of calling some `rustup` command was not successful.
47+
#[error("calling `{command:?}` was not successful")]
48+
CommandFail {
49+
/// The command which was called.
50+
command: Box<Command>,
51+
/// The output of called command.
52+
output: Output,
53+
},
54+
}
55+
56+
impl RustupCallError {
57+
/// Creates [`Io`](RustupCallError::Io) variant from given arguments.
58+
fn io(command: impl Into<Box<Command>>, source: io::Error) -> Self {
59+
Self::Io {
60+
command: command.into(),
61+
source,
62+
}
63+
}
64+
65+
/// Creates [`CommandFail`](RustupCallError::CommandFail) variant from given arguments.
66+
fn command_fail(command: impl Into<Box<Command>>, output: Output) -> Self {
67+
Self::CommandFail {
68+
command: command.into(),
69+
output,
70+
}
71+
}
72+
73+
/// Returns the command which was called.
74+
#[inline]
75+
#[expect(clippy::must_use_candidate, reason = "returns a reference")]
76+
pub fn command(&self) -> &Command {
77+
match self {
78+
Self::Io { command, .. } | Self::CommandFail { command, .. } => command.as_ref(),
79+
}
80+
}
81+
82+
/// Converts self into the command which was called.
83+
#[inline]
84+
#[must_use]
85+
pub fn into_command(self) -> Command {
86+
match self {
87+
Self::Io { command, .. } | Self::CommandFail { command, .. } => *command,
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)