diff --git a/crates/cargo-oxide/src/commands/scaffold.rs b/crates/cargo-oxide/src/commands/scaffold.rs index e02dc36d15..6dd2c33332 100644 --- a/crates/cargo-oxide/src/commands/scaffold.rs +++ b/crates/cargo-oxide/src/commands/scaffold.rs @@ -137,11 +137,11 @@ cuda-core = {{ git = "{GIT_REPO}" }} fn scaffold_main_rs(async_mode: bool) -> String { if async_mode { - r#"use cuda_device::{kernel, thread, DisjointSlice}; -use cuda_host::cuda_module; -use cuda_async::device_context::init_device_contexts; + r#"use cuda_async::device_context::init_device_contexts; use cuda_async::device_operation::DeviceOperation; use cuda_core::LaunchConfig; +use cuda_device::{DisjointSlice, kernel, thread}; +use cuda_host::cuda_module; #[cuda_module] mod kernels { @@ -231,9 +231,9 @@ async fn main() -> Result<(), Box> { "# .to_string() } else { - r#"use cuda_device::{kernel, launch_bounds, launch_contract, thread, DisjointSlice}; + r#"use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig1D}; +use cuda_device::{DisjointSlice, kernel, launch_bounds, launch_contract, thread}; use cuda_host::cuda_module; -use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig1D}; #[cuda_module] mod kernels { diff --git a/crates/cargo-oxide/src/commands/tests.rs b/crates/cargo-oxide/src/commands/tests.rs index 4b0958e311..47753016e0 100644 --- a/crates/cargo-oxide/src/commands/tests.rs +++ b/crates/cargo-oxide/src/commands/tests.rs @@ -4065,9 +4065,10 @@ fn scaffold_sync_template_uses_launch_contract_and_docs() { assert!(files.gitignore.contains("/target/")); // The template uses the launch_bounds / launch_contract attribute // macros, so the cuda_device import must bring them in; a scaffolded - // project fails to compile without this exact line. - assert!(files.main_rs.starts_with( - "use cuda_device::{kernel, launch_bounds, launch_contract, thread, DisjointSlice};" + // project fails to compile without these names. The order is rustfmt's, + // pinned by `scaffold_templates_are_rustfmt_clean`. + assert!(files.main_rs.contains( + "use cuda_device::{DisjointSlice, kernel, launch_bounds, launch_contract, thread};" )); assert!( files @@ -4096,6 +4097,57 @@ fn scaffold_async_template_keeps_async_deps_and_docs() { assert!(!files.main_rs.contains("use cuda_device::{cuda_module")); } +/// A scaffolded project must already be `rustfmt`-clean. +/// +/// CONTRIBUTING points contributors at `cargo oxide fmt`, so a template that is +/// not canonical makes a brand-new project fail its very first format check on +/// code the user never wrote. +/// +/// This runs the real `rustfmt` over the rendered template instead of +/// re-deriving its import ordering here. A hand-written ordering check would +/// agree with a stale template as readily as with a correct one, and would not +/// notice drift anywhere else in the file -- +/// `release_depfile_path_matches_real_cargo_uplift_for_hyphenated_bin` shells +/// out to real `cargo` for the same reason. +/// +/// The edition is read back out of the scaffolded `Cargo.toml` so this cannot +/// drift from the manifest it is meant to describe. +#[test] +fn scaffold_templates_are_rustfmt_clean() { + for (async_mode, label) in [(false, "sync"), (true, "async")] { + let files = scaffold_files("demo_kernel", async_mode); + + let edition = files + .cargo_toml + .lines() + .find_map(|line| line.trim().strip_prefix("edition = ")) + .map(|value| value.trim().trim_matches('"').to_string()) + .unwrap_or_else(|| panic!("{label} scaffold Cargo.toml declares no edition")); + + let root = unique_temp_dir(&format!("cargo_oxide_scaffold_fmt_{label}")); + std::fs::create_dir_all(&root).unwrap(); + let main_rs = root.join("main.rs"); + std::fs::write(&main_rs, &files.main_rs).unwrap(); + + // A scaffolded project carries no rustfmt.toml, so plain defaults for + // its edition are exactly what `cargo fmt` applies there. + let check = Command::new("rustfmt") + .args(["--edition", edition.as_str(), "--check"]) + .arg(&main_rs) + .output() + .expect("failed to run rustfmt; it is in rust-toolchain.toml's component list"); + + let diff = String::from_utf8_lossy(&check.stdout).into_owned(); + std::fs::remove_dir_all(&root).unwrap(); + + assert!( + check.status.success() && diff.is_empty(), + "the {label} scaffold template is not rustfmt-clean, so `cargo oxide fmt \ + --check` fails in a freshly scaffolded project:\n{diff}" + ); + } +} + #[test] fn scaffold_gitignore_covers_every_clean_artifact_suffix() { let gitignore = scaffold_gitignore(); diff --git a/cuda-oxide-book/getting-started/hello-gpu.md b/cuda-oxide-book/getting-started/hello-gpu.md index d6ea1c18a2..1fbbb0e931 100644 --- a/cuda-oxide-book/getting-started/hello-gpu.md +++ b/cuda-oxide-book/getting-started/hello-gpu.md @@ -211,11 +211,11 @@ The `--async` flag generates a project with `tokio` and `cuda-async` dependencie Here's the generated async vecadd template (with minor formatting edits for readability): ```rust -use cuda_device::{kernel, thread, DisjointSlice}; -use cuda_host::cuda_module; use cuda_async::device_context::init_device_contexts; use cuda_async::device_operation::DeviceOperation; use cuda_core::LaunchConfig; +use cuda_device::{DisjointSlice, kernel, thread}; +use cuda_host::cuda_module; #[cuda_module] mod kernels {