From c94aff901009a405c6a25ac4ce9d9ad5eaf743cb Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 15:28:30 +0100 Subject: [PATCH 1/6] copy less strict lint config from rust-gpu --- Cargo.toml | 96 ++++++++++++++++++++++++++++-------- crates/cargo-gpu/src/main.rs | 4 -- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ea70da..26aeba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,28 +43,82 @@ cargo-util-schemas = "0.8.2" semver = "1.0.26" dunce = "1.0.5" + + [workspace.lints.rust] missing_docs = "warn" +# copied from rust-gpu +future_incompatible = "warn" +nonstandard_style = "warn" +rust_2018_idioms = "warn" [workspace.lints.clippy] -all = { level = "warn", priority = 0 } -pedantic = { level = "warn", priority = 0 } -nursery = { level = "warn", priority = 0 } -cargo = { level = "warn", priority = 0 } -restriction = { level = "warn", priority = 0 } -blanket_clippy_restriction_lints = { level = "allow", priority = 1 } - -arithmetic_side_effects = { level = "allow", priority = 1 } -absolute_paths = { level = "allow", priority = 1 } -cargo_common_metadata = { level = "allow", priority = 1 } -implicit_return = { level = "allow", priority = 1 } -single_call_fn = { level = "allow", priority = 1 } -question_mark_used = { level = "allow", priority = 1 } -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 } -std_instead_of_alloc = { level = "allow", priority = 1 } -arbitrary_source_item_ordering = { level = "allow", priority = 1 } -missing_inline_in_public_items = { level = "allow", priority = 1 } -doc_paragraphs_missing_punctuation = { level = "allow", priority = 1 } +print_stdout = "deny" +print_stderr = "deny" +exit = "deny" +# copied from rust-gpu +all = { level = "warn", priority = -1 } +await_holding_lock = "warn" +char_lit_as_u8 = "warn" +checked_conversions = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enums = "warn" +enum_glob_use = "warn" +#exit = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_let_mutex = "warn" +implicit_clone = "warn" +imprecise_flops = "warn" +inefficient_to_string = "warn" +invalid_upcast_comparisons = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_unit_value = "warn" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +manual_ok_or = "warn" +map_err_ignore = "warn" +map_flatten = "warn" +map_unwrap_or = "warn" +match_same_arms = "warn" +match_wild_err_arm = "warn" +match_wildcard_for_single_variants = "warn" +mem_forget = "warn" +missing_enforced_import_renames = "warn" +mut_mut = "warn" +mutex_integer = "warn" +needless_borrow = "warn" +needless_continue = "warn" +needless_for_each = "warn" +option_option = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +single_match_else = "warn" +string_add_assign = "warn" +string_add = "warn" +string_lit_as_bytes = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +unimplemented = "warn" +unnested_or_patterns = "warn" +unused_self = "warn" +useless_transmute = "warn" +verbose_file_reads = "warn" +zero_sized_map_values = "warn" diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index 449aa70..82151f2 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -17,10 +17,6 @@ fn main() { )] { eprintln!("Error: {error}"); - - // `clippy::exit` seems to be a false positive in `main()`. - // See: https://github.com/rust-lang/rust-clippy/issues/13518 - #[expect(clippy::restriction, reason = "Our central place for safely exiting")] std::process::exit(1); }; } From 9c8939274307cb7bb2156acb499bf011dfda9c78 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 13:39:17 +0100 Subject: [PATCH 2/6] toolchain: refactor, check installed components, install everything as one cmd --- .../src/install_toolchain.rs | 197 +++++++++++------- 1 file changed, 125 insertions(+), 72 deletions(-) diff --git a/crates/cargo-gpu-install/src/install_toolchain.rs b/crates/cargo-gpu-install/src/install_toolchain.rs index 300ddb8..6eb4f29 100644 --- a/crates/cargo-gpu-install/src/install_toolchain.rs +++ b/crates/cargo-gpu-install/src/install_toolchain.rs @@ -1,10 +1,16 @@ //! toolchain installation logic +use crate::user_output; use anyhow::Context as _; #[cfg(feature = "tty")] use crossterm::tty::IsTty as _; +use std::collections::HashSet; +use std::process::Command; +use std::string::FromUtf8Error; -use crate::user_output; +/// list of required rustup components +pub const REQUIRED_COMPONENTS: &[&str] = + ["rust-src", "rustc-dev", "llvm-tools", "clippy"].as_slice(); /// Use `rustup` to install the toolchain and components, if not already installed. /// @@ -16,87 +22,134 @@ pub fn ensure_toolchain_and_components_exist( channel: &str, skip_toolchain_install_consent: bool, ) -> anyhow::Result<()> { - // Check for the required toolchain - let output_toolchain_list = std::process::Command::new("rustup") - .args(["toolchain", "list"]) - .output() - .context("running rustup command")?; - anyhow::ensure!( - output_toolchain_list.status.success(), - "could not list installed toolchains" - ); - let string_toolchain_list = String::from_utf8_lossy(&output_toolchain_list.stdout); - if string_toolchain_list - .split_whitespace() - .any(|toolchain| toolchain.starts_with(channel)) - { - log::debug!("toolchain {channel} is already installed"); - } else { - let message = format!("Rust {channel} with `rustup`"); + // While our channel may be `nightly-2024-04-24`, it'll be resolved to the full toolchain name of e.g. + // `nightly-2024-04-24-aarch64-unknown-linux-gnu` and that's also what `rustup toolchain list` will print. + // Only checking whether the toolchain starts with the channel name may incorrectly pass if you have a toolchain + // installed that you're not able to run on your system via `rustup toolchain install --force-non-host ...`. + // CMD: `rustc --print host-tuple` + // TODO: What if the user has no toolchain installed? You can't query this with rustup sady. + let (host_tuple, _) = run_cmd(Command::new("rustc").args(["--print", "host-tuple"]))?; + let host_tuple = host_tuple.trim_ascii(); + let toolchain = format!("{channel}-{host_tuple}"); + + if !is_toolchain_installed(&toolchain, host_tuple)? { + let message = format!( + "toolchain {channel} with components {}", + intersperse(", ", REQUIRED_COMPONENTS.iter().copied()) + ); get_consent_for_toolchain_install( format!("Install {message}").as_ref(), skip_toolchain_install_consent, )?; - crate::user_output!("Installing {message}\n"); - - let output_toolchain_add = std::process::Command::new("rustup") - .args(["toolchain", "add"]) - .arg(channel) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .context("adding toolchain")?; - anyhow::ensure!( - output_toolchain_add.status.success(), - "could not install required toolchain" + user_output!("Installing {message}\n"); + + // component list may be out of sync + // CMD: `rustup toolchain install nightly-2024-04-24 -c clippy,rust-src,rustc-dev,llvm-tools` + run_cmd( + Command::new("rustup") + .args([ + "toolchain", + "install", + &toolchain, + "-c", + &intersperse(",", REQUIRED_COMPONENTS.iter().copied()), + ]) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()), + )?; + } + + Ok(()) +} + +/// Returns true if the toolchain and required components are installed. +fn is_toolchain_installed(toolchain: &str, host_tuple: &str) -> anyhow::Result { + // check if toolchain is installed + // CMD: `rustup toolchain list -q` + let (list_toolchains, _) = run_cmd(Command::new("rustup").args(["toolchain", "list", "-q"]))?; + if !list_toolchains + .split_ascii_whitespace() + .any(|s| s == toolchain) + { + log::info!("toolchain {toolchain} is not installed"); + return Ok(false); + } + + // check if required components are installed + // NOTE: checking for components will install the toolchain with the default profile, if not already installed! + // So we must check beforehand whether the toolchain is installed, to not accidentally install it here. + // Passing *just* `-q` will list available components, so add `--installed` for installed components. + // CMD: `rustup component list --toolchain nightly-2024-04-24-aarch64-unknown-linux-gnu -q --installed` + let (components, _) = run_cmd(Command::new("rustup").args([ + "component", + "list", + "--toolchain", + toolchain, + "-q", + "--installed", + ]))?; + + // components are listed as: + // * `llvm-tools-aarch64-unknown-linux-gnu` and we need to snippet off the host tuple from the end + // * `rust-src` since source code isn't target dependent + let component_host_suffix = format!("-{host_tuple}"); + let mut required = REQUIRED_COMPONENTS.iter().copied().collect::>(); + for component in components.split_ascii_whitespace() { + required.remove( + component + .strip_suffix(&component_host_suffix) + .unwrap_or(component), ); } + if !required.is_empty() { + log::info!("components {required:?} missing for toolchain {toolchain}"); + return Ok(false); + } - // Check for the required components - let output_component_list = std::process::Command::new("rustup") - .args(["component", "list", "--toolchain"]) - .arg(channel) - .output() - .context("getting toolchain list")?; - anyhow::ensure!( - output_component_list.status.success(), - "could not list installed components" - ); - let string_component_list = String::from_utf8_lossy(&output_component_list.stdout); - let required_components = ["rust-src", "rustc-dev", "llvm-tools"]; - let installed_components = string_component_list.lines().collect::>(); - let all_components_installed = required_components.iter().all(|component| { - installed_components.iter().any(|installed_component| { - let is_component = installed_component.starts_with(component); - let is_installed = installed_component.ends_with("(installed)"); - is_component && is_installed - }) - }); - if all_components_installed { - log::debug!("all required components are installed"); - } else { - let message = "toolchain components [rust-src, rustc-dev, llvm-tools] with `rustup`"; - get_consent_for_toolchain_install( - format!("Install {message}").as_ref(), - skip_toolchain_install_consent, - )?; - crate::user_output!("Installing {message}\n"); - - let output_component_add = std::process::Command::new("rustup") - .args(["component", "add", "--toolchain"]) - .arg(channel) - .args(["rust-src", "rustc-dev", "llvm-tools"]) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .context("adding rustup component")?; - anyhow::ensure!( - output_component_add.status.success(), - "could not install required components" + log::info!("toolchain and required components are already installed"); + Ok(true) +} + +pub fn run_cmd(cmd: &mut Command) -> anyhow::Result<(String, String)> { + let output = cmd.output(); + let fmt_cmd = || { + intersperse( + " ", + std::iter::once(cmd.get_program()) + .chain(cmd.get_args()) + .map(|s| s.to_str().unwrap()), + ) + }; + let output = output.with_context(|| format!("Failed to launch cmd `{}`", fmt_cmd()))?; + + let utf8_error = |e: FromUtf8Error, kind: &str| { + anyhow::anyhow!( + "Command `{}` {} contains invalid UTF-8: {} \n {:?}", + kind, + fmt_cmd(), + e.utf8_error(), + e.into_bytes() + ) + }; + let stdout = String::from_utf8(output.stdout).map_err(|e| utf8_error(e, "stdout"))?; + let stderr = String::from_utf8(output.stderr).map_err(|e| utf8_error(e, "stderr"))?; + + if !output.status.success() { + anyhow::bail!( + "Command `{}` failed with {}:\n-- stdout\n{stdout}\n-- stderr\n{stderr}", + fmt_cmd(), + &output.status, ); } + Ok((stdout, stderr)) +} - Ok(()) +/// Folds an [`Iterator`] of `&str` into a [`String`] while interspersing some `&str` between each element +#[expect(clippy::string_add, reason = "Deliberately using String::add")] +fn intersperse<'a>(intersperse: &str, iter: impl Iterator) -> String { + let mut s = iter.fold(String::new(), |a, b| a + b + intersperse); + s.truncate(s.len() - intersperse.len()); + s } #[cfg(not(feature = "tty"))] From 6217ae41892324a5c0ca252f0ccfd120198da526 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 14:50:34 +0100 Subject: [PATCH 3/6] toolchain: make user consent default to yes on enter --- crates/cargo-gpu-install/src/install_toolchain.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/cargo-gpu-install/src/install_toolchain.rs b/crates/cargo-gpu-install/src/install_toolchain.rs index 6eb4f29..cc04257 100644 --- a/crates/cargo-gpu-install/src/install_toolchain.rs +++ b/crates/cargo-gpu-install/src/install_toolchain.rs @@ -181,7 +181,7 @@ fn get_consent_for_toolchain_install( log::debug!("asking for consent to install the required toolchain"); crossterm::terminal::enable_raw_mode().context("enabling raw mode")?; - crate::user_output!("{prompt} [y/n]: "); + user_output!("{prompt} [Y/n]: "); let mut input = crossterm::event::read().context("reading crossterm event")?; if let crossterm::event::Event::Key(crossterm::event::KeyEvent { @@ -196,14 +196,19 @@ fn get_consent_for_toolchain_install( } crossterm::terminal::disable_raw_mode().context("disabling raw mode")?; + #[expect(clippy::print_stdout, reason = "need a newline after crossterm input")] + { + println!(); + } + if let crossterm::event::Event::Key(crossterm::event::KeyEvent { - code: crossterm::event::KeyCode::Char('y'), + code: crossterm::event::KeyCode::Char('y') | crossterm::event::KeyCode::Enter, .. }) = input { Ok(()) } else { - crate::user_output!("Exiting...\n"); + user_output!("Exiting...\n"); #[expect(clippy::exit, reason = "user requested abort")] std::process::exit(0); } From 090a7181a796949e3989dd31c9f8e584aa49cce9 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 14:53:02 +0100 Subject: [PATCH 4/6] toolchain: rustup install with minimal profile --- crates/cargo-gpu-install/src/install_toolchain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/cargo-gpu-install/src/install_toolchain.rs b/crates/cargo-gpu-install/src/install_toolchain.rs index cc04257..ab24fd3 100644 --- a/crates/cargo-gpu-install/src/install_toolchain.rs +++ b/crates/cargo-gpu-install/src/install_toolchain.rs @@ -51,6 +51,8 @@ pub fn ensure_toolchain_and_components_exist( "toolchain", "install", &toolchain, + "--profile", + "minimal", "-c", &intersperse(",", REQUIRED_COMPONENTS.iter().copied()), ]) From 130beb8dc1a0e71d556d7306c0f4fe78d32b5a52 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 14:54:45 +0100 Subject: [PATCH 5/6] toolchain: add minimal profile components in case they are manually uninstalled --- crates/cargo-gpu-install/src/install_toolchain.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/cargo-gpu-install/src/install_toolchain.rs b/crates/cargo-gpu-install/src/install_toolchain.rs index ab24fd3..12e9255 100644 --- a/crates/cargo-gpu-install/src/install_toolchain.rs +++ b/crates/cargo-gpu-install/src/install_toolchain.rs @@ -9,8 +9,16 @@ use std::process::Command; use std::string::FromUtf8Error; /// list of required rustup components -pub const REQUIRED_COMPONENTS: &[&str] = - ["rust-src", "rustc-dev", "llvm-tools", "clippy"].as_slice(); +pub const REQUIRED_COMPONENTS: &[&str] = [ + "cargo", + "rustc", + "rust-std", + "clippy", + "rust-src", + "rustc-dev", + "llvm-tools", +] +.as_slice(); /// Use `rustup` to install the toolchain and components, if not already installed. /// From 220abf76ee5b5e1b2f38099f64e79c2f74659e86 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 10 Feb 2026 16:06:10 +0100 Subject: [PATCH 6/6] update spirv-builder --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d539d3..426e96d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,7 +1011,7 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_codegen_spirv-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=a30bd43db45f2bfe260051f44141e5eaffcbb4b0#a30bd43db45f2bfe260051f44141e5eaffcbb4b0" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=e7e447a9395a77caec5cbddea556af86bc68c573#e7e447a9395a77caec5cbddea556af86bc68c573" dependencies = [ "rspirv", "semver", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "spirv-builder" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=a30bd43db45f2bfe260051f44141e5eaffcbb4b0#a30bd43db45f2bfe260051f44141e5eaffcbb4b0" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=e7e447a9395a77caec5cbddea556af86bc68c573#e7e447a9395a77caec5cbddea556af86bc68c573" dependencies = [ "cargo_metadata", "clap", diff --git a/Cargo.toml b/Cargo.toml index 26aeba3..7aa6c61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ keywords = ["gpu", "compiler", "rust-gpu"] license = "MIT OR Apache-2.0" [workspace.dependencies] -spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "a30bd43db45f2bfe260051f44141e5eaffcbb4b0", default-features = false } +spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "e7e447a9395a77caec5cbddea556af86bc68c573", default-features = false } cargo-gpu-install = { path = "./crates/cargo-gpu-install" } anyhow = "1.0.98" clap = { version = "4.5.41", features = ["derive"] }