Skip to content

Commit b826762

Browse files
committed
split apart spirv_cli into spirv_source, install_toolchain and lockfile
1 parent 2a74f92 commit b826762

File tree

7 files changed

+307
-365
lines changed

7 files changed

+307
-365
lines changed

crates/cargo-gpu/src/args.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@ pub struct InstallArgs {
6363
#[clap(long, verbatim_doc_comment)]
6464
pub spirv_builder_version: Option<String>,
6565

66-
/// Rust toolchain channel to use to build `spirv-builder`.
67-
///
68-
/// This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo.
69-
#[clap(long)]
70-
pub rust_toolchain: Option<String>,
71-
7266
/// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt.
7367
#[clap(long)]
7468
pub force_spirv_cli_rebuild: bool,

crates/cargo-gpu/src/install.rs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use std::io::Write as _;
55
use std::path::Path;
66

77
use crate::args::InstallArgs;
8-
use crate::{cache_dir, spirv_cli::SpirvCli, spirv_source::SpirvSource, target_spec_dir};
8+
use crate::spirv_source::{
9+
get_channel_from_rustc_codegen_spirv_build_script, get_package_from_crate,
10+
};
11+
use crate::{cache_dir, spirv_source::SpirvSource, target_spec_dir};
912

1013
/// Metadata for the compile targets supported by `rust-gpu`
1114
const TARGET_SPECS: &[(&str, &str)] = &[
@@ -80,20 +83,8 @@ pub struct Install {
8083
}
8184

8285
impl Install {
83-
/// Returns a [`SpirvCLI`] instance, responsible for ensuring the right version of the `rustc_codegen_spirv` crate.
84-
fn spirv_cli(&self) -> anyhow::Result<SpirvCli> {
85-
SpirvCli::new(
86-
&self.spirv_install.shader_crate,
87-
self.spirv_install.spirv_builder_source.clone(),
88-
self.spirv_install.spirv_builder_version.clone(),
89-
self.spirv_install.rust_toolchain.clone(),
90-
self.spirv_install.auto_install_rust_toolchain,
91-
self.spirv_install.force_overwrite_lockfiles_v4_to_v3,
92-
)
93-
}
94-
9586
/// Create the `rustc_codegen_spirv_dummy` crate that depends on `rustc_codegen_spirv`
96-
fn write_source_files(spirv_cli: &SpirvCli, checkout: &Path) -> anyhow::Result<()> {
87+
fn write_source_files(source: &SpirvSource, checkout: &Path) -> anyhow::Result<()> {
9788
{
9889
let main = "fn main() {}";
9990
let src = checkout.join("src");
@@ -102,7 +93,7 @@ impl Install {
10293
};
10394

10495
{
105-
let version_spec = match &spirv_cli.source {
96+
let version_spec = match &source {
10697
SpirvSource::CratesIO(version) => {
10798
format!("version = \"{}\"", version)
10899
}
@@ -159,15 +150,14 @@ package = "rustc_codegen_spirv"
159150
format!("could not create cache directory '{}'", cache_dir.display())
160151
})?;
161152

162-
let spirv_version = self.spirv_cli().context("running spirv cli")?;
163-
spirv_version
164-
.ensure_toolchain_and_components_exist()
165-
.context("ensuring toolchain and components exist")?;
166-
167-
let checkout = spirv_version
168-
.cached_checkout_path()
169-
.context("getting cached checkout path")?;
170-
let release = checkout.join("target").join("release");
153+
// TODO what about lockfiles?
154+
// let spirv_version = self.spirv_cli().context("running spirv cli")?;
155+
let source = SpirvSource::new(
156+
&self.spirv_install.shader_crate,
157+
self.spirv_install.spirv_builder_source.as_deref(),
158+
self.spirv_install.spirv_builder_version.as_deref(),
159+
)?;
160+
let checkout = source.install_dir()?;
171161

172162
let dylib_filename = format!(
173163
"{}rustc_codegen_spirv{}",
@@ -186,22 +176,36 @@ package = "rustc_codegen_spirv"
186176
log::info!("...and so we are aborting the install step.");
187177
} else {
188178
log::debug!(
189-
"writing rustc_codegen_spirv_dummy source files into '{}'",
179+
"writing `rustc_codegen_spirv_dummy` source files into '{}'",
190180
checkout.display()
191181
);
192-
Self::write_source_files(&spirv_version, &checkout).context("writing source files")?;
182+
Self::write_source_files(&source, &checkout).context("writing source files")?;
183+
184+
log::debug!("resolving toolchain version to use");
185+
let rustc_codegen_spirv = get_package_from_crate(&checkout, "rustc_codegen_spirv")
186+
.context("get `rustc_codegen_spirv` metadata")?;
187+
let toolchain_channel =
188+
get_channel_from_rustc_codegen_spirv_build_script(&rustc_codegen_spirv)
189+
.context("read toolchain from `rustc_codegen_spirv`'s build.rs")?;
190+
log::info!("selected toolchain channel `{toolchain_channel:?}`");
191+
192+
log::debug!("ensure_toolchain_and_components_exist");
193+
crate::install_toolchain::ensure_toolchain_and_components_exist(
194+
&toolchain_channel,
195+
self.spirv_install.auto_install_rust_toolchain,
196+
)
197+
.context("ensuring toolchain and components exist")?;
198+
199+
log::debug!("write_target_spec_files");
193200
self.write_target_spec_files()
194201
.context("writing target spec files")?;
195202

196-
crate::user_output!(
197-
"Compiling shader-specific `rustc_codegen_spirv` for {}\n",
198-
self.spirv_install.shader_crate.display()
199-
);
203+
crate::user_output!("Compiling `rustc_codegen_spirv` from source {}\n", source,);
200204

201205
let mut build_command = std::process::Command::new("cargo");
202206
build_command
203207
.current_dir(&checkout)
204-
.arg(format!("+{}", spirv_version.channel))
208+
.arg(format!("+{}", toolchain_channel))
205209
.args(["build", "--release"])
206210
.env_remove("RUSTC");
207211

@@ -221,7 +225,10 @@ package = "rustc_codegen_spirv"
221225
})
222226
.context("running build command")?;
223227

224-
let dylib_path = release.join(&dylib_filename);
228+
let dylib_path = checkout
229+
.join("target")
230+
.join("release")
231+
.join(&dylib_filename);
225232
if dylib_path.is_file() {
226233
log::info!("successfully built {}", dylib_path.display());
227234
std::fs::rename(&dylib_path, &dest_dylib_path).context("renaming dylib path")?;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use anyhow::Context;
2+
3+
/// Use `rustup` to install the toolchain and components, if not already installed.
4+
///
5+
/// Pretty much runs:
6+
///
7+
/// * rustup toolchain add nightly-2024-04-24
8+
/// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools
9+
pub fn ensure_toolchain_and_components_exist(
10+
channel: &str,
11+
skip_toolchain_install_consent: bool,
12+
) -> anyhow::Result<()> {
13+
// Check for the required toolchain
14+
let output_toolchain_list = std::process::Command::new("rustup")
15+
.args(["toolchain", "list"])
16+
.output()
17+
.context("running rustup command")?;
18+
anyhow::ensure!(
19+
output_toolchain_list.status.success(),
20+
"could not list installed toolchains"
21+
);
22+
let string_toolchain_list = String::from_utf8_lossy(&output_toolchain_list.stdout);
23+
if string_toolchain_list
24+
.split_whitespace()
25+
.any(|toolchain| toolchain.starts_with(&channel))
26+
{
27+
log::debug!("toolchain {} is already installed", channel);
28+
} else {
29+
let message = format!("Rust {} with `rustup`", channel);
30+
get_consent_for_toolchain_install(
31+
format!("Install {message}").as_ref(),
32+
skip_toolchain_install_consent,
33+
)?;
34+
crate::user_output!("Installing {message}\n");
35+
36+
let output_toolchain_add = std::process::Command::new("rustup")
37+
.args(["toolchain", "add"])
38+
.arg(&channel)
39+
.stdout(std::process::Stdio::inherit())
40+
.stderr(std::process::Stdio::inherit())
41+
.output()
42+
.context("adding toolchain")?;
43+
anyhow::ensure!(
44+
output_toolchain_add.status.success(),
45+
"could not install required toolchain"
46+
);
47+
}
48+
49+
// Check for the required components
50+
let output_component_list = std::process::Command::new("rustup")
51+
.args(["component", "list", "--toolchain"])
52+
.arg(&channel)
53+
.output()
54+
.context("getting toolchain list")?;
55+
anyhow::ensure!(
56+
output_component_list.status.success(),
57+
"could not list installed components"
58+
);
59+
let string_component_list = String::from_utf8_lossy(&output_component_list.stdout);
60+
let required_components = ["rust-src", "rustc-dev", "llvm-tools"];
61+
let installed_components = string_component_list.lines().collect::<Vec<_>>();
62+
let all_components_installed = required_components.iter().all(|component| {
63+
installed_components.iter().any(|installed_component| {
64+
let is_component = installed_component.starts_with(component);
65+
let is_installed = installed_component.ends_with("(installed)");
66+
is_component && is_installed
67+
})
68+
});
69+
if all_components_installed {
70+
log::debug!("all required components are installed");
71+
} else {
72+
let message = "toolchain components [rust-src, rustc-dev, llvm-tools] with `rustup`";
73+
get_consent_for_toolchain_install(
74+
format!("Install {message}").as_ref(),
75+
skip_toolchain_install_consent,
76+
)?;
77+
crate::user_output!("Installing {message}\n");
78+
79+
let output_component_add = std::process::Command::new("rustup")
80+
.args(["component", "add", "--toolchain"])
81+
.arg(&channel)
82+
.args(["rust-src", "rustc-dev", "llvm-tools"])
83+
.stdout(std::process::Stdio::inherit())
84+
.stderr(std::process::Stdio::inherit())
85+
.output()
86+
.context("adding rustup component")?;
87+
anyhow::ensure!(
88+
output_component_add.status.success(),
89+
"could not install required components"
90+
);
91+
}
92+
93+
Ok(())
94+
}
95+
96+
/// Prompt user if they want to install a new Rust toolchain.
97+
fn get_consent_for_toolchain_install(
98+
prompt: &str,
99+
skip_toolchain_install_consent: bool,
100+
) -> anyhow::Result<()> {
101+
if skip_toolchain_install_consent {
102+
return Ok(());
103+
}
104+
log::debug!("asking for consent to install the required toolchain");
105+
crossterm::terminal::enable_raw_mode().context("enabling raw mode")?;
106+
crate::user_output!("{prompt} [y/n]: \n");
107+
let mut input = crossterm::event::read().context("reading crossterm event")?;
108+
109+
if let crossterm::event::Event::Key(crossterm::event::KeyEvent {
110+
code: crossterm::event::KeyCode::Enter,
111+
kind: crossterm::event::KeyEventKind::Release,
112+
..
113+
}) = input
114+
{
115+
// In Powershell, programs will potentially observe the Enter key release after they started
116+
// (see crossterm#124). If that happens, re-read the input.
117+
input = crossterm::event::read().context("re-reading crossterm event")?;
118+
}
119+
crossterm::terminal::disable_raw_mode().context("disabling raw mode")?;
120+
121+
if let crossterm::event::Event::Key(crossterm::event::KeyEvent {
122+
code: crossterm::event::KeyCode::Char('y'),
123+
..
124+
}) = input
125+
{
126+
Ok(())
127+
} else {
128+
crate::user_output!("Exiting...\n");
129+
std::process::exit(0);
130+
}
131+
}

0 commit comments

Comments
 (0)