From ee1993a9f45f06af0bf7c462e5ea47b8963b44da Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 21 Dec 2024 20:49:02 +0100 Subject: [PATCH] Ask user if they want to install new toolchains Also add some user output for the major steps of installing toolchains, compiling `spirv-builder-cli` and compiling the actual shader. Surprisingly there's no way in the standard library to get a single keypress from the user! There's ways to get a line, therefore a keypress then a newline, but that's not so conventional for responding to a "y/n" prompt. Fixes #14 --- Cargo.lock | 151 +++++++++++++++++++++++++++ Cargo.toml | 2 +- README.md | 45 +++++++- crates/cargo-gpu/Cargo.toml | 1 + crates/cargo-gpu/src/build.rs | 8 +- crates/cargo-gpu/src/install.rs | 10 ++ crates/cargo-gpu/src/main.rs | 32 +++++- crates/cargo-gpu/src/show.rs | 4 +- crates/cargo-gpu/src/spirv_cli.rs | 37 ++++++- crates/cargo-gpu/src/spirv_source.rs | 2 + crates/spirv-builder-cli/Cargo.toml | 5 + crates/spirv-builder-cli/src/main.rs | 2 +- justfile | 2 +- 13 files changed, 287 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec3345b..607acfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,7 @@ dependencies = [ "anyhow", "chrono", "clap", + "crossterm", "directories", "env_logger 0.10.2", "http", @@ -164,6 +165,31 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "directories" version = "5.0.1" @@ -231,6 +257,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -338,6 +374,22 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -359,6 +411,18 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -396,6 +460,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -420,6 +507,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -481,12 +577,31 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.214" @@ -537,6 +652,42 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "spirv-builder-cli" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a97e989..a23359e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ resolver = "2" anyhow = "1.0.94" clap = { version = "4.4.8", features = ["derive"] } chrono = { version = "0.4.38", default-features = false, features = ["std"] } +crossterm = "0.28.1" directories = "5.0.1" env_home = "0.1.0" env_logger = "0.10" @@ -49,7 +50,6 @@ multiple_crate_versions = { level = "allow", priority = 1 } pub_with_shorthand = { level = "allow", priority = 1 } partial_pub_fields = { level = "allow", priority = 1 } pattern_type_mismatch = { level = "allow", priority = 1 } -print_stdout = { level = "allow", priority = 1 } std_instead_of_alloc = { level = "allow", priority = 1 } diff --git a/README.md b/README.md index e0a2b1f..ace69f4 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,9 @@ Options: --force-spirv-cli-rebuild Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt + --auto-install-rust-toolchain + Assume "yes" to "Install Rust toolchain: [y/n]" prompt + -h, --help Print help (see a summary with '-h') @@ -134,6 +137,9 @@ Options: --force-spirv-cli-rebuild Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt + --auto-install-rust-toolchain + Assume "yes" to "Install Rust toolchain: [y/n]" prompt + --shader-target Shader target @@ -197,13 +203,44 @@ Options: Show some useful values -Usage: cargo-gpu show [OPTIONS] +Usage: cargo-gpu show -Options: - --cache-directory - Displays the location of the cache directory +Commands: + cache-directory Displays the location of the cache directory + spirv-source The source location of spirv-std + help Print this message or the help of the given subcommand(s) +Options: -h, --help Print help + + * Cache-directory + + Displays the location of the cache directory + + Usage: cargo-gpu show cache-directory + + Options: + -h, --help + Print help + + + * Spirv-source + + The source location of spirv-std + + Usage: cargo-gpu show spirv-source [OPTIONS] + + Options: + --shader-crate + The location of the shader-crate to inspect to determine its spirv-std dependency + + [default: ./] + + -h, --help + Print help + + + ```` diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index e7ad33c..b05cd68 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -21,6 +21,7 @@ serde_json.workspace = true toml.workspace = true chrono.workspace = true http.workspace = true +crossterm.workspace = true [dev-dependencies] test-log.workspace = true diff --git a/crates/cargo-gpu/src/build.rs b/crates/cargo-gpu/src/build.rs index b6f958a..0f7bf68 100644 --- a/crates/cargo-gpu/src/build.rs +++ b/crates/cargo-gpu/src/build.rs @@ -1,7 +1,5 @@ //! `cargo gpu build`, analogous to `cargo build` -use std::io::Write as _; - use anyhow::Context as _; use clap::Parser; use spirv_builder_cli::{Linkage, ShaderModule}; @@ -64,6 +62,11 @@ impl Build { let arg = serde_json::to_string_pretty(&spirv_builder_args)?; log::info!("using spirv-builder-cli arg: {arg}"); + crate::user_output!( + "Running `spirv-builder-cli` to compile shader at {}...\n", + self.install.shader_crate.display() + ); + // Call spirv-builder-cli to compile the shaders. let output = std::process::Command::new(spirv_builder_cli_path) .arg(arg) @@ -112,7 +115,6 @@ impl Build { let manifest_path = self.output_dir.join("manifest.json"); // Sort the contents so the output is deterministic linkage.sort(); - // UNWRAP: safe because we know this always serializes let json = serde_json::to_string_pretty(&linkage)?; let mut file = std::fs::File::create(&manifest_path).with_context(|| { format!( diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index ce02d73..c6e3eef 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -118,6 +118,10 @@ pub struct Install { /// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. #[clap(long)] force_spirv_cli_rebuild: bool, + + /// Assume "yes" to "Install Rust toolchain: [y/n]" prompt. + #[clap(long, action)] + auto_install_rust_toolchain: bool, } impl Install { @@ -128,6 +132,7 @@ impl Install { self.spirv_builder_source.clone(), self.spirv_builder_version.clone(), self.rust_toolchain.clone(), + self.auto_install_rust_toolchain, ) } @@ -230,6 +235,11 @@ impl Install { self.write_source_files()?; self.write_target_spec_files()?; + crate::user_output!( + "Compiling shader-specific `spirv-builder-cli` for {}\n", + self.shader_crate.display() + ); + let mut command = std::process::Command::new("cargo"); command .current_dir(&checkout) diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index 86167c0..f2a026c 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -65,6 +65,36 @@ mod spirv_cli; mod spirv_source; mod toml; +/// Central function to write to the user. +#[macro_export] +macro_rules! user_output { + ($($args: tt)*) => { + #[allow( + clippy::allow_attributes, + reason = "`std::io::Write` is only sometimes called??" + )] + #[allow( + clippy::useless_attribute, + reason = "`std::io::Write` is only sometimes called??" + )] + #[allow( + unused_imports, + reason = "`std::io::Write` is only sometimes called??" + )] + use std::io::Write as _; + + #[expect( + clippy::non_ascii_literal, + reason = "CRAB GOOD. CRAB IMPORTANT." + )] + { + print!("🦀 "); + } + print!($($args)*); + std::io::stdout().flush().unwrap(); + } +} + fn main() -> anyhow::Result<()> { env_logger::builder().init(); @@ -161,7 +191,7 @@ fn dump_full_usage_for_readme() -> anyhow::Result<()> { command.build(); write_help(&mut buffer, &mut command, 0)?; - println!("{}", String::from_utf8(buffer)?); + user_output!("{}", String::from_utf8(buffer)?); Ok(()) } diff --git a/crates/cargo-gpu/src/show.rs b/crates/cargo-gpu/src/show.rs index c1668e2..cc9c623 100644 --- a/crates/cargo-gpu/src/show.rs +++ b/crates/cargo-gpu/src/show.rs @@ -33,12 +33,12 @@ impl Show { log::info!("{:?}: ", self.command); match self.command { Info::CacheDirectory => { - println!("{}", cache_dir()?.display()); + crate::user_output!("{}\n", cache_dir()?.display()); } Info::SpirvSource(SpirvSourceDep { shader_crate }) => { let rust_gpu_source = crate::spirv_source::SpirvSource::get_spirv_std_dep_definition(&shader_crate)?; - println!("{rust_gpu_source}"); + crate::user_output!("{rust_gpu_source}\n"); } } diff --git a/crates/cargo-gpu/src/spirv_cli.rs b/crates/cargo-gpu/src/spirv_cli.rs index bc26ed9..13813f9 100644 --- a/crates/cargo-gpu/src/spirv_cli.rs +++ b/crates/cargo-gpu/src/spirv_cli.rs @@ -23,6 +23,8 @@ pub struct SpirvCli { pub channel: String, /// The date of the pinned version of `rust-gpu` pub date: chrono::NaiveDate, + /// Has the user overridden the toolchain consent prompt + is_toolchain_install_consent: bool, } impl core::fmt::Display for SpirvCli { @@ -42,6 +44,7 @@ impl SpirvCli { maybe_rust_gpu_source: Option, maybe_rust_gpu_version: Option, maybe_rust_gpu_channel: Option, + is_toolchain_install_consent: bool, ) -> anyhow::Result { let (default_rust_gpu_source, rust_gpu_date, default_rust_gpu_channel) = SpirvSource::get_rust_gpu_deps_from_shader(shader_crate_path)?; @@ -62,6 +65,7 @@ impl SpirvCli { source: maybe_spirv_source.unwrap_or(default_rust_gpu_source), channel: maybe_rust_gpu_channel.unwrap_or(default_rust_gpu_channel), date: rust_gpu_date, + is_toolchain_install_consent, }) } @@ -99,6 +103,10 @@ impl SpirvCli { { log::debug!("toolchain {} is already installed", self.channel); } else { + self.get_consent_for_toolchain_install( + format!("Install Rust {} with `rustup`", self.channel).as_ref(), + )?; + let output_toolchain_add = std::process::Command::new("rustup") .args(["toolchain", "add"]) .arg(&self.channel) @@ -133,6 +141,10 @@ impl SpirvCli { if all_components_installed { log::debug!("all required components are installed"); } else { + self.get_consent_for_toolchain_install( + "Install toolchain components (rust-src, rustc-dev, llvm-tools) with `rustup`", + )?; + let output_component_add = std::process::Command::new("rustup") .args(["component", "add", "--toolchain"]) .arg(&self.channel) @@ -148,6 +160,29 @@ impl SpirvCli { Ok(()) } + + /// Prompt user if they want to install a new Rust toolchain. + fn get_consent_for_toolchain_install(&self, prompt: &str) -> anyhow::Result<()> { + if self.is_toolchain_install_consent { + return Ok(()); + } + crossterm::terminal::enable_raw_mode()?; + crate::user_output!("{prompt} [y/n]: "); + let input = crossterm::event::read()?; + crossterm::terminal::disable_raw_mode()?; + crate::user_output!("{:?}\n", input); + + if let crossterm::event::Event::Key(crossterm::event::KeyEvent { + code: crossterm::event::KeyCode::Char('y'), + .. + }) = input + { + Ok(()) + } else { + crate::user_output!("Exiting...\n"); + std::process::exit(0); + } + } } #[cfg(test)] @@ -157,7 +192,7 @@ mod test { #[test_log::test] fn cached_checkout_dir_sanity() { let shader_template_path = crate::test::shader_crate_template_path(); - let spirv = SpirvCli::new(&shader_template_path, None, None, None).unwrap(); + let spirv = SpirvCli::new(&shader_template_path, None, None, None, true).unwrap(); let dir = spirv.cached_checkout_path().unwrap(); let name = dir .file_name() diff --git a/crates/cargo-gpu/src/spirv_source.rs b/crates/cargo-gpu/src/spirv_source.rs index f6e0ac3..d98fde9 100644 --- a/crates/cargo-gpu/src/spirv_source.rs +++ b/crates/cargo-gpu/src/spirv_source.rs @@ -320,6 +320,8 @@ impl SpirvSource { self.to_dirname()?.to_string_lossy().as_ref(), ); + crate::user_output!("Cloning `rust-gpu` repo..."); + let output_clone = std::process::Command::new("git") .args([ "clone", diff --git a/crates/spirv-builder-cli/Cargo.toml b/crates/spirv-builder-cli/Cargo.toml index c89b824..6723c5b 100644 --- a/crates/spirv-builder-cli/Cargo.toml +++ b/crates/spirv-builder-cli/Cargo.toml @@ -41,3 +41,8 @@ package = "spirv-builder" optional = true git = "https://github.com/Rust-GPU/rust-gpu" # ${AUTO-REPLACE-SOURCE} rev = "60dcb82" # ${AUTO-REPLACE-VERSION} + +[lints.rust] +# This crate is most often run by end users compiling their shaders so it's not so relevant +# for them to see warnings. +warnings = "allow" diff --git a/crates/spirv-builder-cli/src/main.rs b/crates/spirv-builder-cli/src/main.rs index 09b2780..496c2cc 100644 --- a/crates/spirv-builder-cli/src/main.rs +++ b/crates/spirv-builder-cli/src/main.rs @@ -99,7 +99,7 @@ fn main() { let mut shaders = vec![]; match module { ModuleResult::MultiModule(modules) => { - anyhow::ensure!(!modules.is_empty(), "No shader modules to compile"); + assert!(!modules.is_empty(), "No shader modules to compile"); for (entry, filepath) in modules.into_iter() { log::debug!("compiled {entry} {}", filepath.display()); shaders.push(ShaderModule::new(entry, filepath)); diff --git a/justfile b/justfile index 1f53c29..ff42105 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ [group: 'ci'] build-shader-template: cargo install --path crates/cargo-gpu - cargo gpu install --shader-crate crates/shader-crate-template + cargo gpu install --shader-crate crates/shader-crate-template --auto-install-rust-toolchain cargo gpu build --shader-crate crates/shader-crate-template --output-dir test-shaders ls -lah test-shaders cat test-shaders/manifest.json