From 2579b5de5878368875ab5d73802827e37175dbd6 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 14 Dec 2024 23:03:56 +0100 Subject: [PATCH 1/4] Refactor each subcommand to its own file No other changes, just moving code. --- crates/cargo-gpu/src/builder.rs | 137 ++++++ crates/cargo-gpu/src/install.rs | 243 +++++++++++ crates/cargo-gpu/src/main.rs | 719 ++------------------------------ crates/cargo-gpu/src/show.rs | 17 + crates/cargo-gpu/src/spirv.rs | 139 ++++++ crates/cargo-gpu/src/toml.rs | 141 +++++++ 6 files changed, 710 insertions(+), 686 deletions(-) create mode 100644 crates/cargo-gpu/src/builder.rs create mode 100644 crates/cargo-gpu/src/install.rs create mode 100644 crates/cargo-gpu/src/show.rs create mode 100644 crates/cargo-gpu/src/spirv.rs create mode 100644 crates/cargo-gpu/src/toml.rs diff --git a/crates/cargo-gpu/src/builder.rs b/crates/cargo-gpu/src/builder.rs new file mode 100644 index 0000000..f92965c --- /dev/null +++ b/crates/cargo-gpu/src/builder.rs @@ -0,0 +1,137 @@ +use std::io::Write; + +use clap::Parser; +use spirv_builder_cli::{Linkage, ShaderModule}; + +use crate::{install::Install, target_spec_dir}; + +#[derive(Parser, Debug)] +pub(crate) struct Build { + #[clap(flatten)] + install: Install, + + /// Directory containing the shader crate to compile. + #[clap(long, default_value = "./")] + pub shader_crate: std::path::PathBuf, + + /// Shader target. + #[clap(long, default_value = "spirv-unknown-vulkan1.2")] + shader_target: String, + + /// Set cargo default-features. + #[clap(long)] + no_default_features: bool, + + /// Set cargo features. + #[clap(long)] + features: Vec, + + /// Path to the output directory for the compiled shaders. + #[clap(long, short, default_value = "./")] + pub output_dir: std::path::PathBuf, +} + +impl Build { + pub fn run(&mut self) { + let (dylib_path, spirv_builder_cli_path) = self.install.run(); + + // Ensure the shader output dir exists + log::debug!("ensuring output-dir '{}' exists", self.output_dir.display()); + std::fs::create_dir_all(&self.output_dir).unwrap(); + self.output_dir = self.output_dir.canonicalize().unwrap(); + + // Ensure the shader crate exists + self.shader_crate = self.shader_crate.canonicalize().unwrap(); + assert!( + self.shader_crate.exists(), + "shader crate '{}' does not exist. (Current dir is '{}')", + self.shader_crate.display(), + std::env::current_dir().unwrap().display() + ); + + let spirv_builder_args = spirv_builder_cli::Args { + dylib_path, + shader_crate: self.shader_crate.clone(), + shader_target: self.shader_target.clone(), + path_to_target_spec: target_spec_dir().join(format!("{}.json", self.shader_target)), + no_default_features: self.no_default_features, + features: self.features.clone(), + output_dir: self.output_dir.clone(), + }; + + // UNWRAP: safe because we know this always serializes + let arg = serde_json::to_string_pretty(&spirv_builder_args).unwrap(); + log::info!("using spirv-builder-cli arg: {arg}"); + + // Call spirv-builder-cli to compile the shaders. + let output = std::process::Command::new(spirv_builder_cli_path) + .arg(arg) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .output() + .unwrap(); + assert!(output.status.success(), "build failed"); + + let spirv_manifest = self.output_dir.join("spirv-manifest.json"); + if spirv_manifest.is_file() { + log::debug!( + "successfully built shaders, raw manifest is at '{}'", + spirv_manifest.display() + ); + } else { + log::error!("missing raw manifest '{}'", spirv_manifest.display()); + panic!("missing raw manifest"); + } + + let shaders: Vec = + serde_json::from_reader(std::fs::File::open(&spirv_manifest).unwrap()).unwrap(); + + let mut linkage: Vec<_> = shaders + .into_iter() + .map( + |ShaderModule { + entry, + path: filepath, + }| { + use relative_path::PathExt; + let path = self.output_dir.join(filepath.file_name().unwrap()); + std::fs::copy(&filepath, &path).unwrap(); + let path_relative_to_shader_crate = + path.relative_to(&self.shader_crate).unwrap().to_path(""); + Linkage::new(entry, path_relative_to_shader_crate) + }, + ) + .collect(); + + // Write the shader manifest json file + 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).unwrap(); + let mut file = std::fs::File::create(&manifest_path).unwrap_or_else(|e| { + log::error!( + "could not create shader manifest file '{}': {e}", + manifest_path.display(), + ); + panic!("{e}") + }); + file.write_all(json.as_bytes()).unwrap_or_else(|e| { + log::error!( + "could not write shader manifest file '{}': {e}", + manifest_path.display(), + ); + panic!("{e}") + }); + + log::info!("wrote manifest to '{}'", manifest_path.display()); + + if spirv_manifest.is_file() { + log::debug!( + "removing spirv-manifest.json file '{}'", + spirv_manifest.display() + ); + std::fs::remove_file(spirv_manifest).unwrap(); + } + } +} diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs new file mode 100644 index 0000000..e884bc5 --- /dev/null +++ b/crates/cargo-gpu/src/install.rs @@ -0,0 +1,243 @@ +use std::io::Write; + +use crate::{cache_dir, spirv::Spirv, target_spec_dir}; + +const SPIRV_BUILDER_CLI_CARGO_TOML: &str = include_str!("../../spirv-builder-cli/Cargo.toml"); +const SPIRV_BUILDER_CLI_MAIN: &str = include_str!("../../spirv-builder-cli/src/main.rs"); +const SPIRV_BUILDER_CLI_LIB: &str = include_str!("../../spirv-builder-cli/src/lib.rs"); +const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[ + ("Cargo.toml", SPIRV_BUILDER_CLI_CARGO_TOML), + ("src/main.rs", SPIRV_BUILDER_CLI_MAIN), + ("src/lib.rs", SPIRV_BUILDER_CLI_LIB), +]; + +const TARGET_SPECS: &[(&str, &str)] = &[ + ( + "spirv-unknown-opengl4.0.json", + include_str!("../target-specs/spirv-unknown-opengl4.0.json"), + ), + ( + "spirv-unknown-opengl4.1.json", + include_str!("../target-specs/spirv-unknown-opengl4.1.json"), + ), + ( + "spirv-unknown-opengl4.2.json", + include_str!("../target-specs/spirv-unknown-opengl4.2.json"), + ), + ( + "spirv-unknown-opengl4.3.json", + include_str!("../target-specs/spirv-unknown-opengl4.3.json"), + ), + ( + "spirv-unknown-opengl4.5.json", + include_str!("../target-specs/spirv-unknown-opengl4.5.json"), + ), + ( + "spirv-unknown-spv1.0.json", + include_str!("../target-specs/spirv-unknown-spv1.0.json"), + ), + ( + "spirv-unknown-spv1.1.json", + include_str!("../target-specs/spirv-unknown-spv1.1.json"), + ), + ( + "spirv-unknown-spv1.2.json", + include_str!("../target-specs/spirv-unknown-spv1.2.json"), + ), + ( + "spirv-unknown-spv1.3.json", + include_str!("../target-specs/spirv-unknown-spv1.3.json"), + ), + ( + "spirv-unknown-spv1.4.json", + include_str!("../target-specs/spirv-unknown-spv1.4.json"), + ), + ( + "spirv-unknown-spv1.5.json", + include_str!("../target-specs/spirv-unknown-spv1.5.json"), + ), + ( + "spirv-unknown-vulkan1.0.json", + include_str!("../target-specs/spirv-unknown-vulkan1.0.json"), + ), + ( + "spirv-unknown-vulkan1.1.json", + include_str!("../target-specs/spirv-unknown-vulkan1.1.json"), + ), + ( + "spirv-unknown-vulkan1.1spv1.4.json", + include_str!("../target-specs/spirv-unknown-vulkan1.1spv1.4.json"), + ), + ( + "spirv-unknown-vulkan1.2.json", + include_str!("../target-specs/spirv-unknown-vulkan1.2.json"), + ), +]; + +#[derive(clap::Parser, Debug)] +pub(crate) struct Install { + /// spirv-builder dependency, written just like in a Cargo.toml file. + #[clap(long, default_value = Spirv::DEFAULT_DEP)] + spirv_builder: String, + + /// Rust toolchain channel to use to build `spirv-builder`. + /// + /// This must match the `spirv_builder` argument. + #[clap(long, default_value = Spirv::DEFAULT_CHANNEL)] + rust_toolchain: String, + + /// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. + #[clap(long)] + force_spirv_cli_rebuild: bool, +} + +impl Install { + fn spirv_cli(&self) -> Spirv { + Spirv { + dep: self.spirv_builder.clone(), + channel: self.rust_toolchain.clone(), + } + } + + fn write_source_files(&self) { + let cli = self.spirv_cli(); + let checkout = cli.cached_checkout_path(); + std::fs::create_dir_all(checkout.join("src")).unwrap(); + for (filename, contents) in SPIRV_BUILDER_FILES.iter() { + log::debug!("writing {filename}"); + let path = checkout.join(filename); + let mut file = std::fs::File::create(&path).unwrap(); + let replaced_contents = contents + .replace("${SPIRV_BUILDER_SOURCE}", &cli.dep) + .replace("${CHANNEL}", &cli.channel); + file.write_all(replaced_contents.as_bytes()).unwrap(); + } + } + + fn write_target_spec_files(&self) { + for (filename, contents) in TARGET_SPECS.iter() { + let path = target_spec_dir().join(filename); + if !path.is_file() || self.force_spirv_cli_rebuild { + let mut file = std::fs::File::create(&path).unwrap(); + file.write_all(contents.as_bytes()).unwrap(); + } + } + } + + // Install the binary pair and return the paths, (dylib, cli). + pub fn run(&self) -> (std::path::PathBuf, std::path::PathBuf) { + // Ensure the cache dir exists + let cache_dir = cache_dir(); + log::info!("cache directory is '{}'", cache_dir.display()); + std::fs::create_dir_all(&cache_dir).unwrap_or_else(|e| { + log::error!( + "could not create cache directory '{}': {e}", + cache_dir.display() + ); + panic!("could not create cache dir"); + }); + + let spirv_version = self.spirv_cli(); + spirv_version.ensure_version_channel_compatibility(); + spirv_version.ensure_toolchain_and_components_exist(); + + let checkout = spirv_version.cached_checkout_path(); + let release = checkout.join("target").join("release"); + + let dylib_filename = format!( + "{}rustc_codegen_spirv{}", + std::env::consts::DLL_PREFIX, + std::env::consts::DLL_SUFFIX + ); + let dylib_path = release.join(&dylib_filename); + let dest_dylib_path = checkout.join(&dylib_filename); + let dest_cli_path = checkout.join("spirv-builder-cli"); + if dest_dylib_path.is_file() && dest_cli_path.is_file() { + log::info!( + "cargo-gpu artifacts are already installed in '{}'", + checkout.display() + ); + } + + if dest_dylib_path.is_file() && dest_cli_path.is_file() && !self.force_spirv_cli_rebuild { + log::info!("...and so we are aborting the install step."); + } else { + log::debug!( + "writing spirv-builder-cli source files into '{}'", + checkout.display() + ); + self.write_source_files(); + self.write_target_spec_files(); + + let mut command = std::process::Command::new("cargo"); + command + .current_dir(&checkout) + .arg(format!("+{}", spirv_version.channel)) + .args(["build", "--release"]) + .args(["--no-default-features"]); + + command.args([ + "--features", + &Self::get_required_spirv_builder_version(spirv_version.channel), + ]); + + log::debug!("building artifacts with `{:?}`", command); + + let output = command + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .output() + .unwrap(); + assert!(output.status.success(), "...build error!"); + + if dylib_path.is_file() { + log::info!("successfully built {}", dylib_path.display()); + std::fs::rename(&dylib_path, &dest_dylib_path).unwrap(); + } else { + log::error!("could not find {}", dylib_path.display()); + panic!("spirv-builder-cli build failed"); + } + + let cli_path = if cfg!(target_os = "windows") { + release.join("spirv-builder-cli").with_extension("exe") + } else { + release.join("spirv-builder-cli") + }; + if cli_path.is_file() { + log::info!("successfully built {}", cli_path.display()); + std::fs::rename(&cli_path, &dest_cli_path).unwrap(); + } else { + log::error!("could not find {}", cli_path.display()); + log::debug!("contents of '{}':", release.display()); + for entry in std::fs::read_dir(&release).unwrap() { + let entry = entry.unwrap(); + log::debug!("{}", entry.file_name().to_string_lossy()); + } + panic!("spirv-builder-cli build failed"); + } + } + (dest_dylib_path, dest_cli_path) + } + + /// The `spirv-builder` crate from the main `rust-gpu` repo hasn't always been setup to + /// interact with `cargo-gpu`. Older versions don't have the same `SpirvBuilder` interface. So + /// here we choose the right Cargo feature to enable/disable code in `spirv-builder-cli`. + /// + /// TODO: + /// * Download the actual `rust-gpu` repo as pinned in the shader's `Cargo.lock` and get the + /// `spirv-builder` version from there. + /// * Warn the user that certain `cargo-gpu` features aren't available when building with + /// older versions of `spirv-builder`, eg setting the target spec. + fn get_required_spirv_builder_version(toolchain_channel: String) -> String { + let parse_date = chrono::NaiveDate::parse_from_str; + let datetime = parse_date(&toolchain_channel, "nightly-%Y-%m-%d").unwrap(); + let pre_cli_date = parse_date("2024-04-24", "%Y-%m-%d").unwrap(); + + if datetime < pre_cli_date { + "spirv-builder-pre-cli" + } else { + "spirv-builder-0_10" + } + .into() + } +} diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index e79e83d..575dd41 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -49,220 +49,18 @@ //! manifest file can be used by build scripts (`build.rs` files) to generate linkage or //! conduct other post-processing, like converting the `spv` files into `wgsl` files, //! for example. -use std::io::Write; -use clap::{Parser, Subcommand}; -use spirv_builder_cli::{Linkage, ShaderModule}; +use builder::Build; +use clap::Parser; +use install::Install; +use show::Show; +use toml::Toml; -const SPIRV_BUILDER_CLI_CARGO_TOML: &str = include_str!("../../spirv-builder-cli/Cargo.toml"); -const SPIRV_BUILDER_CLI_MAIN: &str = include_str!("../../spirv-builder-cli/src/main.rs"); -const SPIRV_BUILDER_CLI_LIB: &str = include_str!("../../spirv-builder-cli/src/lib.rs"); -const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[ - ("Cargo.toml", SPIRV_BUILDER_CLI_CARGO_TOML), - ("src/main.rs", SPIRV_BUILDER_CLI_MAIN), - ("src/lib.rs", SPIRV_BUILDER_CLI_LIB), -]; - -const SPIRV_STD_TOOLCHAIN_PAIRS: &[(&str, &str)] = &[("0.10", "nightly-2024-04-24")]; - -const TARGET_SPECS: &[(&str, &str)] = &[ - ( - "spirv-unknown-opengl4.0.json", - include_str!("../target-specs/spirv-unknown-opengl4.0.json"), - ), - ( - "spirv-unknown-opengl4.1.json", - include_str!("../target-specs/spirv-unknown-opengl4.1.json"), - ), - ( - "spirv-unknown-opengl4.2.json", - include_str!("../target-specs/spirv-unknown-opengl4.2.json"), - ), - ( - "spirv-unknown-opengl4.3.json", - include_str!("../target-specs/spirv-unknown-opengl4.3.json"), - ), - ( - "spirv-unknown-opengl4.5.json", - include_str!("../target-specs/spirv-unknown-opengl4.5.json"), - ), - ( - "spirv-unknown-spv1.0.json", - include_str!("../target-specs/spirv-unknown-spv1.0.json"), - ), - ( - "spirv-unknown-spv1.1.json", - include_str!("../target-specs/spirv-unknown-spv1.1.json"), - ), - ( - "spirv-unknown-spv1.2.json", - include_str!("../target-specs/spirv-unknown-spv1.2.json"), - ), - ( - "spirv-unknown-spv1.3.json", - include_str!("../target-specs/spirv-unknown-spv1.3.json"), - ), - ( - "spirv-unknown-spv1.4.json", - include_str!("../target-specs/spirv-unknown-spv1.4.json"), - ), - ( - "spirv-unknown-spv1.5.json", - include_str!("../target-specs/spirv-unknown-spv1.5.json"), - ), - ( - "spirv-unknown-vulkan1.0.json", - include_str!("../target-specs/spirv-unknown-vulkan1.0.json"), - ), - ( - "spirv-unknown-vulkan1.1.json", - include_str!("../target-specs/spirv-unknown-vulkan1.1.json"), - ), - ( - "spirv-unknown-vulkan1.1spv1.4.json", - include_str!("../target-specs/spirv-unknown-vulkan1.1spv1.4.json"), - ), - ( - "spirv-unknown-vulkan1.2.json", - include_str!("../target-specs/spirv-unknown-vulkan1.2.json"), - ), -]; - -/// Cargo dependency for `spirv-builder` and the rust toolchain channel. -#[derive(Debug, Clone)] -struct Spirv { - dep: String, - channel: String, -} - -impl Default for Spirv { - fn default() -> Self { - Self { - dep: Self::DEFAULT_DEP.into(), - channel: Self::DEFAULT_CHANNEL.into(), - } - } -} - -impl core::fmt::Display for Spirv { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - format!("{}+{}", self.dep, self.channel).fmt(f) - } -} - -impl Spirv { - const DEFAULT_DEP: &str = r#"{ git = "https://github.com/Rust-GPU/rust-gpu.git" }"#; - const DEFAULT_CHANNEL: &str = "nightly-2024-04-24"; - - /// Returns a string suitable to use as a directory. - /// - /// Created from the spirv-builder source dep and the rustc channel. - fn to_dirname(&self) -> String { - self.to_string() - .replace( - [std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='], - "_", - ) - .split(['{', '}', ' ', '\n', '"', '\'']) - .collect::>() - .concat() - } - - fn cached_checkout_path(&self) -> std::path::PathBuf { - let checkout_dir = cache_dir().join(self.to_dirname()); - std::fs::create_dir_all(&checkout_dir).unwrap_or_else(|e| { - log::error!( - "could not create checkout dir '{}': {e}", - checkout_dir.display() - ); - panic!("could not create checkout dir"); - }); - - checkout_dir - } - - fn ensure_version_channel_compatibility(&self) { - for (version, channel) in SPIRV_STD_TOOLCHAIN_PAIRS.iter() { - if version.starts_with(&self.dep) && channel != &self.channel { - panic!("expected spirv-std version to be matched with rust toolchain channel {channel}"); - } - } - } - - /// Use `rustup` to install the toolchain and components, if not already installed. - /// - /// Pretty much runs: - /// - /// * rustup toolchain add nightly-2024-04-24 - /// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools - fn ensure_toolchain_and_components_exist(&self) { - // Check for the required toolchain - let output = std::process::Command::new("rustup") - .args(["toolchain", "list"]) - .output() - .unwrap(); - assert!( - output.status.success(), - "could not list installed toolchains" - ); - let stdout = String::from_utf8_lossy(&output.stdout); - if stdout - .split_whitespace() - .any(|toolchain| toolchain.starts_with(&self.channel)) - { - log::debug!("toolchain {} is already installed", self.channel); - } else { - let output = std::process::Command::new("rustup") - .args(["toolchain", "add"]) - .arg(&self.channel) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .unwrap(); - assert!( - output.status.success(), - "could not install required toolchain" - ); - } - - // Check for the required components - let output = std::process::Command::new("rustup") - .args(["component", "list", "--toolchain"]) - .arg(&self.channel) - .output() - .unwrap(); - assert!( - output.status.success(), - "could not list installed components" - ); - let stdout = String::from_utf8_lossy(&output.stdout); - let required_components = ["rust-src", "rustc-dev", "llvm-tools"]; - let installed_components = stdout.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 output = std::process::Command::new("rustup") - .args(["component", "add", "--toolchain"]) - .arg(&self.channel) - .args(["rust-src", "rustc-dev", "llvm-tools"]) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .unwrap(); - assert!( - output.status.success(), - "could not install required components" - ); - } - } -} +mod builder; +mod install; +mod show; +mod spirv; +mod toml; fn target_spec_dir() -> std::path::PathBuf { let dir = cache_dir().join("target-specs"); @@ -270,460 +68,31 @@ fn target_spec_dir() -> std::path::PathBuf { dir } -#[derive(Parser, Debug)] -struct Install { - /// spirv-builder dependency, written just like in a Cargo.toml file. - #[clap(long, default_value = Spirv::DEFAULT_DEP)] - spirv_builder: String, - - /// Rust toolchain channel to use to build `spirv-builder`. - /// - /// This must match the `spirv_builder` argument. - #[clap(long, default_value = Spirv::DEFAULT_CHANNEL)] - rust_toolchain: String, - - /// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. - #[clap(long)] - force_spirv_cli_rebuild: bool, -} - -impl Install { - fn spirv_cli(&self) -> Spirv { - Spirv { - dep: self.spirv_builder.clone(), - channel: self.rust_toolchain.clone(), - } - } - - fn write_source_files(&self) { - let cli = self.spirv_cli(); - let checkout = cli.cached_checkout_path(); - std::fs::create_dir_all(checkout.join("src")).unwrap(); - for (filename, contents) in SPIRV_BUILDER_FILES.iter() { - log::debug!("writing {filename}"); - let path = checkout.join(filename); - let mut file = std::fs::File::create(&path).unwrap(); - let replaced_contents = contents - .replace("${SPIRV_BUILDER_SOURCE}", &cli.dep) - .replace("${CHANNEL}", &cli.channel); - file.write_all(replaced_contents.as_bytes()).unwrap(); - } - } - - fn write_target_spec_files(&self) { - for (filename, contents) in TARGET_SPECS.iter() { - let path = target_spec_dir().join(filename); - if !path.is_file() || self.force_spirv_cli_rebuild { - let mut file = std::fs::File::create(&path).unwrap(); - file.write_all(contents.as_bytes()).unwrap(); - } - } - } - - // Install the binary pair and return the paths, (dylib, cli). - fn run(&self) -> (std::path::PathBuf, std::path::PathBuf) { - // Ensure the cache dir exists - let cache_dir = cache_dir(); - log::info!("cache directory is '{}'", cache_dir.display()); - std::fs::create_dir_all(&cache_dir).unwrap_or_else(|e| { - log::error!( - "could not create cache directory '{}': {e}", - cache_dir.display() - ); - panic!("could not create cache dir"); - }); - - let spirv_version = self.spirv_cli(); - spirv_version.ensure_version_channel_compatibility(); - spirv_version.ensure_toolchain_and_components_exist(); - - let checkout = spirv_version.cached_checkout_path(); - let release = checkout.join("target").join("release"); - - let dylib_filename = format!( - "{}rustc_codegen_spirv{}", - std::env::consts::DLL_PREFIX, - std::env::consts::DLL_SUFFIX - ); - let dylib_path = release.join(&dylib_filename); - let dest_dylib_path = checkout.join(&dylib_filename); - let dest_cli_path = checkout.join("spirv-builder-cli"); - if dest_dylib_path.is_file() && dest_cli_path.is_file() { - log::info!( - "cargo-gpu artifacts are already installed in '{}'", - checkout.display() - ); - } - - if dest_dylib_path.is_file() && dest_cli_path.is_file() && !self.force_spirv_cli_rebuild { - log::info!("...and so we are aborting the install step."); - } else { - log::debug!( - "writing spirv-builder-cli source files into '{}'", - checkout.display() - ); - self.write_source_files(); - self.write_target_spec_files(); - - let mut command = std::process::Command::new("cargo"); - command - .current_dir(&checkout) - .arg(format!("+{}", spirv_version.channel)) - .args(["build", "--release"]) - .args(["--no-default-features"]); - - command.args([ - "--features", - &Self::get_required_spirv_builder_version(spirv_version.channel), - ]); - - log::debug!("building artifacts with `{:?}`", command); - - let output = command - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .unwrap(); - assert!(output.status.success(), "...build error!"); - - if dylib_path.is_file() { - log::info!("successfully built {}", dylib_path.display()); - std::fs::rename(&dylib_path, &dest_dylib_path).unwrap(); - } else { - log::error!("could not find {}", dylib_path.display()); - panic!("spirv-builder-cli build failed"); - } - - let cli_path = if cfg!(target_os = "windows") { - release.join("spirv-builder-cli").with_extension("exe") - } else { - release.join("spirv-builder-cli") - }; - if cli_path.is_file() { - log::info!("successfully built {}", cli_path.display()); - std::fs::rename(&cli_path, &dest_cli_path).unwrap(); - } else { - log::error!("could not find {}", cli_path.display()); - log::debug!("contents of '{}':", release.display()); - for entry in std::fs::read_dir(&release).unwrap() { - let entry = entry.unwrap(); - log::debug!("{}", entry.file_name().to_string_lossy()); - } - panic!("spirv-builder-cli build failed"); - } - } - (dest_dylib_path, dest_cli_path) - } - - /// The `spirv-builder` crate from the main `rust-gpu` repo hasn't always been setup to - /// interact with `cargo-gpu`. Older versions don't have the same `SpirvBuilder` interface. So - /// here we choose the right Cargo feature to enable/disable code in `spirv-builder-cli`. - /// - /// TODO: - /// * Download the actual `rust-gpu` repo as pinned in the shader's `Cargo.lock` and get the - /// `spirv-builder` version from there. - /// * Warn the user that certain `cargo-gpu` features aren't available when building with - /// older versions of `spirv-builder`, eg setting the target spec. - fn get_required_spirv_builder_version(toolchain_channel: String) -> String { - let parse_date = chrono::NaiveDate::parse_from_str; - let datetime = parse_date(&toolchain_channel, "nightly-%Y-%m-%d").unwrap(); - let pre_cli_date = parse_date("2024-04-24", "%Y-%m-%d").unwrap(); - - if datetime < pre_cli_date { - "spirv-builder-pre-cli" - } else { - "spirv-builder-0_10" - } - .into() - } -} - -#[derive(Parser, Debug)] -struct Build { - #[clap(flatten)] - install: Install, - - /// Directory containing the shader crate to compile. - #[clap(long, default_value = "./")] - shader_crate: std::path::PathBuf, - - /// Shader target. - #[clap(long, default_value = "spirv-unknown-vulkan1.2")] - shader_target: String, - - /// Set cargo default-features. - #[clap(long)] - no_default_features: bool, - - /// Set cargo features. - #[clap(long)] - features: Vec, - - /// Path to the output directory for the compiled shaders. - #[clap(long, short, default_value = "./")] - output_dir: std::path::PathBuf, -} - -impl Build { - fn run(&mut self) { - let (dylib_path, spirv_builder_cli_path) = self.install.run(); - - // Ensure the shader output dir exists - log::debug!("ensuring output-dir '{}' exists", self.output_dir.display()); - std::fs::create_dir_all(&self.output_dir).unwrap(); - self.output_dir = self.output_dir.canonicalize().unwrap(); - - // Ensure the shader crate exists - self.shader_crate = self.shader_crate.canonicalize().unwrap(); - assert!( - self.shader_crate.exists(), - "shader crate '{}' does not exist. (Current dir is '{}')", - self.shader_crate.display(), - std::env::current_dir().unwrap().display() - ); - - let spirv_builder_args = spirv_builder_cli::Args { - dylib_path, - shader_crate: self.shader_crate.clone(), - shader_target: self.shader_target.clone(), - path_to_target_spec: target_spec_dir().join(format!("{}.json", self.shader_target)), - no_default_features: self.no_default_features, - features: self.features.clone(), - output_dir: self.output_dir.clone(), - }; - - // UNWRAP: safe because we know this always serializes - let arg = serde_json::to_string_pretty(&spirv_builder_args).unwrap(); - log::info!("using spirv-builder-cli arg: {arg}"); - - // Call spirv-builder-cli to compile the shaders. - let output = std::process::Command::new(spirv_builder_cli_path) - .arg(arg) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .output() - .unwrap(); - assert!(output.status.success(), "build failed"); - - let spirv_manifest = self.output_dir.join("spirv-manifest.json"); - if spirv_manifest.is_file() { - log::debug!( - "successfully built shaders, raw manifest is at '{}'", - spirv_manifest.display() - ); - } else { - log::error!("missing raw manifest '{}'", spirv_manifest.display()); - panic!("missing raw manifest"); - } - - let shaders: Vec = - serde_json::from_reader(std::fs::File::open(&spirv_manifest).unwrap()).unwrap(); - - let mut linkage: Vec<_> = shaders - .into_iter() - .map( - |ShaderModule { - entry, - path: filepath, - }| { - use relative_path::PathExt; - let path = self.output_dir.join(filepath.file_name().unwrap()); - std::fs::copy(&filepath, &path).unwrap(); - let path_relative_to_shader_crate = - path.relative_to(&self.shader_crate).unwrap().to_path(""); - Linkage::new(entry, path_relative_to_shader_crate) - }, - ) - .collect(); - - // Write the shader manifest json file - 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).unwrap(); - let mut file = std::fs::File::create(&manifest_path).unwrap_or_else(|e| { - log::error!( - "could not create shader manifest file '{}': {e}", - manifest_path.display(), - ); - panic!("{e}") - }); - file.write_all(json.as_bytes()).unwrap_or_else(|e| { - log::error!( - "could not write shader manifest file '{}': {e}", - manifest_path.display(), - ); - panic!("{e}") - }); - - log::info!("wrote manifest to '{}'", manifest_path.display()); - - if spirv_manifest.is_file() { - log::debug!( - "removing spirv-manifest.json file '{}'", - spirv_manifest.display() - ); - std::fs::remove_file(spirv_manifest).unwrap(); - } - } -} - -#[derive(Parser)] -struct Toml { - /// Path to a workspace or package Cargo.toml file. - /// - /// Must include a [[workspace | package].metadata.rust-gpu.build] section where - /// arguments to `cargo gpu build` are listed. - /// - /// Path arguments like `output-dir` and `shader-manifest` must be relative to - /// the location of the Cargo.toml file. - /// - /// Example: - /// - /// ```toml - /// [package.metadata.rust-gpu.build.spirv-builder] - /// git = "https://github.com/Rust-GPU/rust-gpu.git" - /// rev = "0da80f8" - /// - /// [package.metadata.rust-gpu.build] - /// output-dir = "shaders" - /// shader-manifest = "shaders/manifest.json" - /// ``` - /// - /// Calling `cargo gpu toml {path/to/Cargo.toml}` with a Cargo.toml that - /// contains the example above would compile the crate and place the compiled - /// `.spv` files and manifest in a directory "shaders". - #[clap(default_value = "./Cargo.toml", verbatim_doc_comment)] - path: std::path::PathBuf, -} - -impl Toml { - fn run(&self) { - // Find the path to the toml file to use - let path = if self.path.is_file() && self.path.ends_with(".toml") { - self.path.clone() - } else { - let path = self.path.join("Cargo.toml"); - if path.is_file() { - path - } else { - log::error!("toml file '{}' is not a file", self.path.display()); - panic!("toml file '{}' is not a file", self.path.display()); - } - }; - - log::info!("using toml file '{}'", path.display()); - - // Determine if this is a workspace's Cargo.toml or a crate's Cargo.toml - let contents = std::fs::read_to_string(&path).unwrap(); - let toml: toml::Table = toml::from_str(&contents).unwrap(); - - fn get_metadata_rustgpu_table<'a>( - toml: &'a toml::Table, - toml_type: &'static str, - ) -> Option<&'a toml::Table> { - let workspace = toml.get(toml_type)?.as_table()?; - let metadata = workspace.get("metadata")?.as_table()?; - metadata.get("rust-gpu")?.as_table() - } - - let (toml_type, table) = if toml.contains_key("workspace") { - let table = get_metadata_rustgpu_table(&toml, "workspace") - .unwrap_or_else(|| { - panic!( - "toml file '{}' is missing a [workspace.metadata.rust-gpu] table", - path.display() - ); - }) - .clone(); - ("workspace", table) - } else if toml.contains_key("package") { - let mut table = get_metadata_rustgpu_table(&toml, "package") - .unwrap_or_else(|| { - panic!( - "toml file '{}' is missing a [package.metadata.rust-gpu] table", - path.display() - ); - }) - .clone(); - // Ensure the package name is included as the shader-crate parameter - if !table.contains_key("shader-crate") { - table.insert( - "shader-crate".to_string(), - format!("{}", path.parent().unwrap().display()).into(), - ); - } - ("package", table) - } else { - panic!("toml file '{}' must describe a workspace containing [workspace.metadata.rust-gpu.build] or a describe a crate with [package.metadata.rust-gpu.build]", path.display()); - }; - log::info!( - "building with [{toml_type}.metadata.rust-gpu.build] section of the toml file at '{}'", - path.display() - ); - log::debug!("table: {table:#?}"); - - let mut parameters = table - .get("build") - .unwrap_or_else(|| panic!("toml is missing the 'build' table")) - .as_table() - .unwrap_or_else(|| { - panic!("toml file's '{toml_type}.metadata.rust-gpu.build' property is not a table") - }) - .into_iter() - .flat_map(|(k, v)| match v { - toml::Value::String(s) => [format!("--{k}"), s.clone()], - _ => { - let mut value = String::new(); - let ser = toml::ser::ValueSerializer::new(&mut value); - serde::Serialize::serialize(v, ser).unwrap(); - [format!("--{k}"), value] - } - }) - .collect::>(); - parameters.insert(0, "cargo-gpu".to_string()); - parameters.insert(1, "build".to_string()); - - let working_directory = path.parent().unwrap(); - log::info!( - "issuing cargo commands from the working directory '{}'", - working_directory.display() - ); - std::env::set_current_dir(working_directory).unwrap(); - - log::debug!("build parameters: {parameters:#?}"); - if let Cli { - command: Command::Build(mut build), - .. - } = Cli::parse_from(parameters) - { - log::debug!("build: {build:?}"); - build.run(); - } else { - log::error!("parameters found in [{toml_type}.metadata.rust-gpu.build] were not parameters to `cargo gpu build`"); - panic!("could not determin build command"); - } - } -} +fn main() { + env_logger::builder().init(); -#[derive(Parser)] -struct Show { - #[clap(long)] - /// Displays the location of the cache directory - cache_directory: bool, -} + let args = std::env::args() + .filter(|p| { + // Calling cargo-gpu as the cargo subcommand "cargo gpu" passes "gpu" + // as the first parameter, which we want to ignore. + p != "gpu" + }) + .collect::>(); + log::trace!("args: {args:?}"); + let cli = Cli::parse_from(args); -impl Show { - fn run(self) { - if self.cache_directory { - log::info!("cache_directory: "); - println!("{}", cache_dir().display()); + match cli.command { + Command::Install(install) => { + let _ = install.run(); } + Command::Build(mut build) => build.run(), + Command::Toml(toml) => toml.run(), + Command::Show(show) => show.run(), + Command::DumpUsage => dump_full_usage_for_readme(), } } -#[derive(Subcommand)] +#[derive(clap::Subcommand)] enum Command { /// Install rust-gpu compiler artifacts. Install(Install), @@ -745,9 +114,9 @@ enum Command { DumpUsage, } -#[derive(Parser)] +#[derive(clap::Parser)] #[clap(author, version, about, subcommand_required = true)] -struct Cli { +pub(crate) struct Cli { /// The command to run. #[clap(subcommand)] command: Command, @@ -763,30 +132,6 @@ fn cache_dir() -> std::path::PathBuf { .join("rust-gpu") } -fn main() { - env_logger::builder().init(); - - let args = std::env::args() - .filter(|p| { - // Calling cargo-gpu as the cargo subcommand "cargo gpu" passes "gpu" - // as the first parameter, which we want to ignore. - p != "gpu" - }) - .collect::>(); - log::trace!("args: {args:?}"); - let cli = Cli::parse_from(args); - - match cli.command { - Command::Install(install) => { - let _ = install.run(); - } - Command::Build(mut build) => build.run(), - Command::Toml(toml) => toml.run(), - Command::Show(show) => show.run(), - Command::DumpUsage => dump_full_usage_for_readme(), - } -} - fn dump_full_usage_for_readme() { use clap::CommandFactory; let mut command = Cli::command(); @@ -822,6 +167,8 @@ fn write_help(buffer: &mut impl std::io::Write, cmd: &mut clap::Command, _depth: #[cfg(test)] mod test { + use spirv::Spirv; + use super::*; #[test] diff --git a/crates/cargo-gpu/src/show.rs b/crates/cargo-gpu/src/show.rs new file mode 100644 index 0000000..1dd727e --- /dev/null +++ b/crates/cargo-gpu/src/show.rs @@ -0,0 +1,17 @@ +use crate::cache_dir; + +#[derive(clap::Parser)] +pub(crate) struct Show { + #[clap(long)] + /// Displays the location of the cache directory + cache_directory: bool, +} + +impl Show { + pub fn run(self) { + if self.cache_directory { + log::info!("cache_directory: "); + println!("{}", cache_dir().display()); + } + } +} diff --git a/crates/cargo-gpu/src/spirv.rs b/crates/cargo-gpu/src/spirv.rs new file mode 100644 index 0000000..6ab58da --- /dev/null +++ b/crates/cargo-gpu/src/spirv.rs @@ -0,0 +1,139 @@ +use crate::cache_dir; + +const SPIRV_STD_TOOLCHAIN_PAIRS: &[(&str, &str)] = &[("0.10", "nightly-2024-04-24")]; + +/// Cargo dependency for `spirv-builder` and the rust toolchain channel. +#[derive(Debug, Clone)] +pub(crate) struct Spirv { + pub dep: String, + pub channel: String, +} + +impl Default for Spirv { + fn default() -> Self { + Self { + dep: Self::DEFAULT_DEP.into(), + channel: Self::DEFAULT_CHANNEL.into(), + } + } +} + +impl core::fmt::Display for Spirv { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + format!("{}+{}", self.dep, self.channel).fmt(f) + } +} + +impl Spirv { + pub const DEFAULT_DEP: &str = r#"{ git = "https://github.com/Rust-GPU/rust-gpu.git" }"#; + pub const DEFAULT_CHANNEL: &str = "nightly-2024-04-24"; + + /// Returns a string suitable to use as a directory. + /// + /// Created from the spirv-builder source dep and the rustc channel. + fn to_dirname(&self) -> String { + self.to_string() + .replace( + [std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='], + "_", + ) + .split(['{', '}', ' ', '\n', '"', '\'']) + .collect::>() + .concat() + } + + pub fn cached_checkout_path(&self) -> std::path::PathBuf { + let checkout_dir = cache_dir().join(self.to_dirname()); + std::fs::create_dir_all(&checkout_dir).unwrap_or_else(|e| { + log::error!( + "could not create checkout dir '{}': {e}", + checkout_dir.display() + ); + panic!("could not create checkout dir"); + }); + + checkout_dir + } + + pub fn ensure_version_channel_compatibility(&self) { + for (version, channel) in SPIRV_STD_TOOLCHAIN_PAIRS.iter() { + if version.starts_with(&self.dep) && channel != &self.channel { + panic!("expected spirv-std version to be matched with rust toolchain channel {channel}"); + } + } + } + + /// Use `rustup` to install the toolchain and components, if not already installed. + /// + /// Pretty much runs: + /// + /// * rustup toolchain add nightly-2024-04-24 + /// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools + pub fn ensure_toolchain_and_components_exist(&self) { + // Check for the required toolchain + let output = std::process::Command::new("rustup") + .args(["toolchain", "list"]) + .output() + .unwrap(); + assert!( + output.status.success(), + "could not list installed toolchains" + ); + let stdout = String::from_utf8_lossy(&output.stdout); + if stdout + .split_whitespace() + .any(|toolchain| toolchain.starts_with(&self.channel)) + { + log::debug!("toolchain {} is already installed", self.channel); + } else { + let output = std::process::Command::new("rustup") + .args(["toolchain", "add"]) + .arg(&self.channel) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .output() + .unwrap(); + assert!( + output.status.success(), + "could not install required toolchain" + ); + } + + // Check for the required components + let output = std::process::Command::new("rustup") + .args(["component", "list", "--toolchain"]) + .arg(&self.channel) + .output() + .unwrap(); + assert!( + output.status.success(), + "could not list installed components" + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let required_components = ["rust-src", "rustc-dev", "llvm-tools"]; + let installed_components = stdout.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 output = std::process::Command::new("rustup") + .args(["component", "add", "--toolchain"]) + .arg(&self.channel) + .args(["rust-src", "rustc-dev", "llvm-tools"]) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .output() + .unwrap(); + assert!( + output.status.success(), + "could not install required components" + ); + } + } +} diff --git a/crates/cargo-gpu/src/toml.rs b/crates/cargo-gpu/src/toml.rs new file mode 100644 index 0000000..bf3785f --- /dev/null +++ b/crates/cargo-gpu/src/toml.rs @@ -0,0 +1,141 @@ +use clap::Parser; + +use crate::{Cli, Command}; + +#[derive(Parser)] +pub(crate) struct Toml { + /// Path to a workspace or package Cargo.toml file. + /// + /// Must include a [[workspace | package].metadata.rust-gpu.build] section where + /// arguments to `cargo gpu build` are listed. + /// + /// Path arguments like `output-dir` and `shader-manifest` must be relative to + /// the location of the Cargo.toml file. + /// + /// Example: + /// + /// ```toml + /// [package.metadata.rust-gpu.build.spirv-builder] + /// git = "https://github.com/Rust-GPU/rust-gpu.git" + /// rev = "0da80f8" + /// + /// [package.metadata.rust-gpu.build] + /// output-dir = "shaders" + /// shader-manifest = "shaders/manifest.json" + /// ``` + /// + /// Calling `cargo gpu toml {path/to/Cargo.toml}` with a Cargo.toml that + /// contains the example above would compile the crate and place the compiled + /// `.spv` files and manifest in a directory "shaders". + #[clap(default_value = "./Cargo.toml", verbatim_doc_comment)] + path: std::path::PathBuf, +} + +impl Toml { + pub fn run(&self) { + // Find the path to the toml file to use + let path = if self.path.is_file() && self.path.ends_with(".toml") { + self.path.clone() + } else { + let path = self.path.join("Cargo.toml"); + if path.is_file() { + path + } else { + log::error!("toml file '{}' is not a file", self.path.display()); + panic!("toml file '{}' is not a file", self.path.display()); + } + }; + + log::info!("using toml file '{}'", path.display()); + + // Determine if this is a workspace's Cargo.toml or a crate's Cargo.toml + let contents = std::fs::read_to_string(&path).unwrap(); + let toml: toml::Table = toml::from_str(&contents).unwrap(); + + fn get_metadata_rustgpu_table<'a>( + toml: &'a toml::Table, + toml_type: &'static str, + ) -> Option<&'a toml::Table> { + let workspace = toml.get(toml_type)?.as_table()?; + let metadata = workspace.get("metadata")?.as_table()?; + metadata.get("rust-gpu")?.as_table() + } + + let (toml_type, table) = if toml.contains_key("workspace") { + let table = get_metadata_rustgpu_table(&toml, "workspace") + .unwrap_or_else(|| { + panic!( + "toml file '{}' is missing a [workspace.metadata.rust-gpu] table", + path.display() + ); + }) + .clone(); + ("workspace", table) + } else if toml.contains_key("package") { + let mut table = get_metadata_rustgpu_table(&toml, "package") + .unwrap_or_else(|| { + panic!( + "toml file '{}' is missing a [package.metadata.rust-gpu] table", + path.display() + ); + }) + .clone(); + // Ensure the package name is included as the shader-crate parameter + if !table.contains_key("shader-crate") { + table.insert( + "shader-crate".to_string(), + format!("{}", path.parent().unwrap().display()).into(), + ); + } + ("package", table) + } else { + panic!("toml file '{}' must describe a workspace containing [workspace.metadata.rust-gpu.build] or a describe a crate with [package.metadata.rust-gpu.build]", path.display()); + }; + log::info!( + "building with [{toml_type}.metadata.rust-gpu.build] section of the toml file at '{}'", + path.display() + ); + log::debug!("table: {table:#?}"); + + let mut parameters = table + .get("build") + .unwrap_or_else(|| panic!("toml is missing the 'build' table")) + .as_table() + .unwrap_or_else(|| { + panic!("toml file's '{toml_type}.metadata.rust-gpu.build' property is not a table") + }) + .into_iter() + .flat_map(|(k, v)| match v { + toml::Value::String(s) => [format!("--{k}"), s.clone()], + _ => { + let mut value = String::new(); + let ser = toml::ser::ValueSerializer::new(&mut value); + serde::Serialize::serialize(v, ser).unwrap(); + [format!("--{k}"), value] + } + }) + .collect::>(); + parameters.insert(0, "cargo-gpu".to_string()); + parameters.insert(1, "build".to_string()); + + let working_directory = path.parent().unwrap(); + log::info!( + "issuing cargo commands from the working directory '{}'", + working_directory.display() + ); + std::env::set_current_dir(working_directory).unwrap(); + + log::debug!("build parameters: {parameters:#?}"); + if let Cli { + command: Command::Build(mut build), + .. + } = Cli::parse_from(parameters) + { + log::debug!("build: {build:?}"); + build.run(); + } else { + log::error!("parameters found in [{toml_type}.metadata.rust-gpu.build] were not parameters to `cargo gpu build`"); + panic!("could not determin build command"); + } + } +} From 708fefc36e4031f4dc371f9ee000aeaf43af93f6 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 14 Dec 2024 23:56:56 +0100 Subject: [PATCH 2/4] Add stricter lints and make them happy --- .github/workflows/push.yaml | 12 +++---- Cargo.toml | 31 ++++++++++++++++- crates/cargo-gpu/Cargo.toml | 6 +++- crates/cargo-gpu/src/builder.rs | 23 ++++++++----- crates/cargo-gpu/src/install.rs | 51 +++++++++++++++++---------- crates/cargo-gpu/src/main.rs | 49 ++++++++++++++------------ crates/cargo-gpu/src/show.rs | 6 +++- crates/cargo-gpu/src/spirv.rs | 61 ++++++++++++++++++++++----------- crates/cargo-gpu/src/toml.rs | 47 +++++++++++++------------ 9 files changed, 186 insertions(+), 100 deletions(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 62f638e..6d561da 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -1,8 +1,8 @@ name: push -on: +on: push: - branches: + branches: - main pull_request: @@ -13,18 +13,18 @@ env: jobs: install-and-build-shaders: strategy: - matrix: + matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} defaults: - run: + run: shell: bash - env: + env: RUST_LOG: debug steps: - uses: actions/checkout@v2 - uses: moonrepo/setup-rust@v1 - - run: rustup default stable + - run: rustup default stable - run: rustup update - run: cargo test - run: cargo install --path crates/cargo-gpu diff --git a/Cargo.toml b/Cargo.toml index 7272a77..9dd7fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "crates/cargo-gpu", + "crates/cargo-gpu", "crates/shader-crate-template" ] @@ -21,3 +21,32 @@ relative-path = "1.9.3" serde = { version = "1.0.214", features = ["derive"] } serde_json = "1.0.132" toml = "0.8.19" + +[workspace.lints.rust] +missing_docs = "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 } +print_stdout = { level = "allow", priority = 1 } +std_instead_of_alloc = { level = "allow", priority = 1 } + +# TODO: Try to not depend on these lints +unwrap_used = { level = "allow", priority = 1 } +panic = { level = "allow", priority = 1 } + diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index 0bdd10f..0935ad9 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -3,8 +3,9 @@ name = "cargo-gpu" version = "0.1.0" edition = "2021" description = "Generates shader .spv files from rust-gpu shader crates" +repository = "https://github.com/Rust-GPU/cargo-gpu" readme = "../../README.md" - +keywords = ["gpu", "compiler"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -33,3 +34,6 @@ codegen-units = 256 opt-level = 3 incremental = true codegen-units = 256 + +[lints] +workspace = true diff --git a/crates/cargo-gpu/src/builder.rs b/crates/cargo-gpu/src/builder.rs index f92965c..665dd20 100644 --- a/crates/cargo-gpu/src/builder.rs +++ b/crates/cargo-gpu/src/builder.rs @@ -1,12 +1,16 @@ -use std::io::Write; +//! `cargo gpu build`, analogous to `cargo build` + +use std::io::Write as _; use clap::Parser; use spirv_builder_cli::{Linkage, ShaderModule}; use crate::{install::Install, target_spec_dir}; +/// `cargo build` subcommands #[derive(Parser, Debug)] -pub(crate) struct Build { +pub struct Build { + /// Install the `rust-gpu` compiler and components #[clap(flatten)] install: Install, @@ -32,6 +36,7 @@ pub(crate) struct Build { } impl Build { + /// Entrypoint pub fn run(&mut self) { let (dylib_path, spirv_builder_cli_path) = self.install.run(); @@ -93,7 +98,7 @@ impl Build { entry, path: filepath, }| { - use relative_path::PathExt; + use relative_path::PathExt as _; let path = self.output_dir.join(filepath.file_name().unwrap()); std::fs::copy(&filepath, &path).unwrap(); let path_relative_to_shader_crate = @@ -109,19 +114,19 @@ impl Build { linkage.sort(); // UNWRAP: safe because we know this always serializes let json = serde_json::to_string_pretty(&linkage).unwrap(); - let mut file = std::fs::File::create(&manifest_path).unwrap_or_else(|e| { + let mut file = std::fs::File::create(&manifest_path).unwrap_or_else(|error| { log::error!( - "could not create shader manifest file '{}': {e}", + "could not create shader manifest file '{}': {error}", manifest_path.display(), ); - panic!("{e}") + panic!("{error}") }); - file.write_all(json.as_bytes()).unwrap_or_else(|e| { + file.write_all(json.as_bytes()).unwrap_or_else(|error| { log::error!( - "could not write shader manifest file '{}': {e}", + "could not write shader manifest file '{}': {error}", manifest_path.display(), ); - panic!("{e}") + panic!("{error}") }); log::info!("wrote manifest to '{}'", manifest_path.display()); diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index e884bc5..0e31f2b 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -1,16 +1,25 @@ -use std::io::Write; +//! Install a dedicated per-shader crate that has the `rust-gpu` compiler in it. +use std::io::Write as _; use crate::{cache_dir, spirv::Spirv, target_spec_dir}; -const SPIRV_BUILDER_CLI_CARGO_TOML: &str = include_str!("../../spirv-builder-cli/Cargo.toml"); -const SPIRV_BUILDER_CLI_MAIN: &str = include_str!("../../spirv-builder-cli/src/main.rs"); -const SPIRV_BUILDER_CLI_LIB: &str = include_str!("../../spirv-builder-cli/src/lib.rs"); +/// These are the files needed to create the dedicated, per-shader `rust-gpu` builder create. const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[ - ("Cargo.toml", SPIRV_BUILDER_CLI_CARGO_TOML), - ("src/main.rs", SPIRV_BUILDER_CLI_MAIN), - ("src/lib.rs", SPIRV_BUILDER_CLI_LIB), + ( + "Cargo.toml", + include_str!("../../spirv-builder-cli/Cargo.toml"), + ), + ( + "src/main.rs", + include_str!("../../spirv-builder-cli/src/main.rs"), + ), + ( + "src/lib.rs", + include_str!("../../spirv-builder-cli/src/lib.rs"), + ), ]; +/// Metadata for the compile targets supported by `rust-gpu` const TARGET_SPECS: &[(&str, &str)] = &[ ( "spirv-unknown-opengl4.0.json", @@ -74,15 +83,16 @@ const TARGET_SPECS: &[(&str, &str)] = &[ ), ]; +/// `cargo gpu install` #[derive(clap::Parser, Debug)] -pub(crate) struct Install { +pub struct Install { /// spirv-builder dependency, written just like in a Cargo.toml file. #[clap(long, default_value = Spirv::DEFAULT_DEP)] spirv_builder: String, /// Rust toolchain channel to use to build `spirv-builder`. /// - /// This must match the `spirv_builder` argument. + /// This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. #[clap(long, default_value = Spirv::DEFAULT_CHANNEL)] rust_toolchain: String, @@ -92,6 +102,7 @@ pub(crate) struct Install { } impl Install { + /// Returns a [`Spirv`] instance, responsible for ensuring the right version of the `spirv-builder-cli` crate. fn spirv_cli(&self) -> Spirv { Spirv { dep: self.spirv_builder.clone(), @@ -99,11 +110,12 @@ impl Install { } } + /// Create the `spirv-builder-cli` crate. fn write_source_files(&self) { let cli = self.spirv_cli(); let checkout = cli.cached_checkout_path(); std::fs::create_dir_all(checkout.join("src")).unwrap(); - for (filename, contents) in SPIRV_BUILDER_FILES.iter() { + for (filename, contents) in SPIRV_BUILDER_FILES { log::debug!("writing {filename}"); let path = checkout.join(filename); let mut file = std::fs::File::create(&path).unwrap(); @@ -114,8 +126,9 @@ impl Install { } } + /// Add the target spec files to the crate. fn write_target_spec_files(&self) { - for (filename, contents) in TARGET_SPECS.iter() { + for (filename, contents) in TARGET_SPECS { let path = target_spec_dir().join(filename); if !path.is_file() || self.force_spirv_cli_rebuild { let mut file = std::fs::File::create(&path).unwrap(); @@ -124,14 +137,14 @@ impl Install { } } - // Install the binary pair and return the paths, (dylib, cli). + /// Install the binary pair and return the paths, (dylib, cli). pub fn run(&self) -> (std::path::PathBuf, std::path::PathBuf) { // Ensure the cache dir exists let cache_dir = cache_dir(); log::info!("cache directory is '{}'", cache_dir.display()); - std::fs::create_dir_all(&cache_dir).unwrap_or_else(|e| { + std::fs::create_dir_all(&cache_dir).unwrap_or_else(|error| { log::error!( - "could not create cache directory '{}': {e}", + "could not create cache directory '{}': {error}", cache_dir.display() ); panic!("could not create cache dir"); @@ -178,7 +191,7 @@ impl Install { command.args([ "--features", - &Self::get_required_spirv_builder_version(spirv_version.channel), + &Self::get_required_spirv_builder_version(&spirv_version.channel), ]); log::debug!("building artifacts with `{:?}`", command); @@ -209,8 +222,8 @@ impl Install { } else { log::error!("could not find {}", cli_path.display()); log::debug!("contents of '{}':", release.display()); - for entry in std::fs::read_dir(&release).unwrap() { - let entry = entry.unwrap(); + for maybe_entry in std::fs::read_dir(&release).unwrap() { + let entry = maybe_entry.unwrap(); log::debug!("{}", entry.file_name().to_string_lossy()); } panic!("spirv-builder-cli build failed"); @@ -228,9 +241,9 @@ impl Install { /// `spirv-builder` version from there. /// * Warn the user that certain `cargo-gpu` features aren't available when building with /// older versions of `spirv-builder`, eg setting the target spec. - fn get_required_spirv_builder_version(toolchain_channel: String) -> String { + fn get_required_spirv_builder_version(toolchain_channel: &str) -> String { let parse_date = chrono::NaiveDate::parse_from_str; - let datetime = parse_date(&toolchain_channel, "nightly-%Y-%m-%d").unwrap(); + let datetime = parse_date(toolchain_channel, "nightly-%Y-%m-%d").unwrap(); let pre_cli_date = parse_date("2024-04-24", "%Y-%m-%d").unwrap(); if datetime < pre_cli_date { diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index 575dd41..3390012 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -51,7 +51,7 @@ //! for example. use builder::Build; -use clap::Parser; +use clap::Parser as _; use install::Install; use show::Show; use toml::Toml; @@ -62,20 +62,14 @@ mod show; mod spirv; mod toml; -fn target_spec_dir() -> std::path::PathBuf { - let dir = cache_dir().join("target-specs"); - std::fs::create_dir_all(&dir).unwrap(); - dir -} - fn main() { env_logger::builder().init(); let args = std::env::args() - .filter(|p| { + .filter(|arg| { // Calling cargo-gpu as the cargo subcommand "cargo gpu" passes "gpu" // as the first parameter, which we want to ignore. - p != "gpu" + arg != "gpu" }) .collect::>(); log::trace!("args: {args:?}"); @@ -83,7 +77,7 @@ fn main() { match cli.command { Command::Install(install) => { - let _ = install.run(); + let (_, _) = install.run(); } Command::Build(mut build) => build.run(), Command::Toml(toml) => toml.run(), @@ -92,6 +86,7 @@ fn main() { } } +/// All of the available subcommands for `cargo gpu` #[derive(clap::Subcommand)] enum Command { /// Install rust-gpu compiler artifacts. @@ -132,35 +127,46 @@ fn cache_dir() -> std::path::PathBuf { .join("rust-gpu") } +/// Location of the target spec metadata files +fn target_spec_dir() -> std::path::PathBuf { + let dir = cache_dir().join("target-specs"); + std::fs::create_dir_all(&dir).unwrap(); + dir +} + +/// Convenience function for internal use. Dumps all the CLI usage instructions. Useful for +/// updating the README. fn dump_full_usage_for_readme() { - use clap::CommandFactory; + use clap::CommandFactory as _; let mut command = Cli::command(); - let mut buffer: Vec = Default::default(); + let mut buffer: Vec = Vec::default(); command.build(); write_help(&mut buffer, &mut command, 0); - let buffer = String::from_utf8(buffer).unwrap(); - println!("{}", buffer); + println!("{}", String::from_utf8(buffer).unwrap()); } +/// Recursive function to print the usage instructions for each subcommand. fn write_help(buffer: &mut impl std::io::Write, cmd: &mut clap::Command, _depth: usize) { if cmd.get_name() == "help" { return; } - let mut command = cmd.get_name().to_string(); - let _ = writeln!( + let mut command = cmd.get_name().to_owned(); + writeln!( buffer, "\n* {}{}", command.remove(0).to_uppercase(), command - ); - let _ = writeln!(buffer); - let _ = cmd.write_long_help(buffer); + ) + .unwrap(); + writeln!(buffer).unwrap(); + cmd.write_long_help(buffer).unwrap(); for sub in cmd.get_subcommands_mut() { - let _ = writeln!(buffer); + writeln!(buffer).unwrap(); + #[expect(clippy::used_underscore_binding, reason = "Used in recursion only")] write_help(buffer, sub, _depth + 1); } } @@ -180,7 +186,7 @@ mod test { .file_name() .unwrap() .to_str() - .map(|s| s.to_string()) + .map(std::string::ToString::to_string) .unwrap(); assert_eq!( "git_https___github_com_Rust-GPU_rust-gpu_git+nightly-2024-04-24", @@ -202,7 +208,6 @@ mod test { ]; if let Cli { command: Command::Build(build), - .. } = Cli::parse_from(args) { assert_eq!(shader_crate, build.shader_crate); diff --git a/crates/cargo-gpu/src/show.rs b/crates/cargo-gpu/src/show.rs index 1dd727e..24d5632 100644 --- a/crates/cargo-gpu/src/show.rs +++ b/crates/cargo-gpu/src/show.rs @@ -1,13 +1,17 @@ +//! Display various information about `cargo gpu`, eg its cache directory. + use crate::cache_dir; +/// `cargo gpu show` #[derive(clap::Parser)] -pub(crate) struct Show { +pub struct Show { #[clap(long)] /// Displays the location of the cache directory cache_directory: bool, } impl Show { + /// Entrypoint pub fn run(self) { if self.cache_directory { log::info!("cache_directory: "); diff --git a/crates/cargo-gpu/src/spirv.rs b/crates/cargo-gpu/src/spirv.rs index 6ab58da..4c114b0 100644 --- a/crates/cargo-gpu/src/spirv.rs +++ b/crates/cargo-gpu/src/spirv.rs @@ -1,11 +1,19 @@ +//! Install the relevant Rust compiler and toolchain components required by the version of +//! `rust-gpu` defined in the shader. use crate::cache_dir; +/// The canonical pairs of `rust-gpu` version to `rust-gpu` Rust toolchain channel. +/// +/// TODO: We may be able to automate this by checking out the locked version of `spirv-std` in the +/// shader's crate. const SPIRV_STD_TOOLCHAIN_PAIRS: &[(&str, &str)] = &[("0.10", "nightly-2024-04-24")]; /// Cargo dependency for `spirv-builder` and the rust toolchain channel. #[derive(Debug, Clone)] -pub(crate) struct Spirv { +pub struct Spirv { + /// The version of `rust-gpu`, eg `"0.10"` or `"{ git = "https://github.com/Rust-GPU/rust-gpu.git" }` pub dep: String, + //// The toolchain that `rust-gpu` uses, eg "nightly-2024-04-24" pub channel: String, } @@ -19,13 +27,19 @@ impl Default for Spirv { } impl core::fmt::Display for Spirv { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[expect( + clippy::min_ident_chars, + reason = "It's a core library trait implementation" + )] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { format!("{}+{}", self.dep, self.channel).fmt(f) } } impl Spirv { + /// The default `rust-gpu` dependency defintion pub const DEFAULT_DEP: &str = r#"{ git = "https://github.com/Rust-GPU/rust-gpu.git" }"#; + /// The default `rust-gpu` toolchain pub const DEFAULT_CHANNEL: &str = "nightly-2024-04-24"; /// Returns a string suitable to use as a directory. @@ -42,11 +56,12 @@ impl Spirv { .concat() } + /// Create and/or return the cache directory pub fn cached_checkout_path(&self) -> std::path::PathBuf { let checkout_dir = cache_dir().join(self.to_dirname()); - std::fs::create_dir_all(&checkout_dir).unwrap_or_else(|e| { + std::fs::create_dir_all(&checkout_dir).unwrap_or_else(|error| { log::error!( - "could not create checkout dir '{}': {e}", + "could not create checkout dir '{}': {error}", checkout_dir.display() ); panic!("could not create checkout dir"); @@ -55,11 +70,17 @@ impl Spirv { checkout_dir } + /// Ensure that the requested `rust-gpu` version/toolchain are known to be canonically + /// compatible. + /// + /// TODO: We may be able to automatically get the pair by downloading the `rust-gpu` repo as defined by the + /// `spriv-std` dependency in the shader. pub fn ensure_version_channel_compatibility(&self) { - for (version, channel) in SPIRV_STD_TOOLCHAIN_PAIRS.iter() { - if version.starts_with(&self.dep) && channel != &self.channel { - panic!("expected spirv-std version to be matched with rust toolchain channel {channel}"); - } + for (version, channel) in SPIRV_STD_TOOLCHAIN_PAIRS { + assert!( + !(version.starts_with(&self.dep) && channel != &self.channel), + "expected spirv-std version to be matched with rust toolchain channel {channel}" + ); } } @@ -71,22 +92,22 @@ impl Spirv { /// * rustup component add --toolchain nightly-2024-04-24 rust-src rustc-dev llvm-tools pub fn ensure_toolchain_and_components_exist(&self) { // Check for the required toolchain - let output = std::process::Command::new("rustup") + let output_toolchain_list = std::process::Command::new("rustup") .args(["toolchain", "list"]) .output() .unwrap(); assert!( - output.status.success(), + output_toolchain_list.status.success(), "could not list installed toolchains" ); - let stdout = String::from_utf8_lossy(&output.stdout); - if stdout + let string_toolchain_list = String::from_utf8_lossy(&output_toolchain_list.stdout); + if string_toolchain_list .split_whitespace() .any(|toolchain| toolchain.starts_with(&self.channel)) { log::debug!("toolchain {} is already installed", self.channel); } else { - let output = std::process::Command::new("rustup") + let output_toolchain_add = std::process::Command::new("rustup") .args(["toolchain", "add"]) .arg(&self.channel) .stdout(std::process::Stdio::inherit()) @@ -94,24 +115,24 @@ impl Spirv { .output() .unwrap(); assert!( - output.status.success(), + output_toolchain_add.status.success(), "could not install required toolchain" ); } // Check for the required components - let output = std::process::Command::new("rustup") + let output_component_list = std::process::Command::new("rustup") .args(["component", "list", "--toolchain"]) .arg(&self.channel) .output() .unwrap(); assert!( - output.status.success(), + output_component_list.status.success(), "could not list installed components" ); - let stdout = String::from_utf8_lossy(&output.stdout); + let string_component_list = String::from_utf8_lossy(&output_component_list.stdout); let required_components = ["rust-src", "rustc-dev", "llvm-tools"]; - let installed_components = stdout.lines().collect::>(); + 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); @@ -122,7 +143,7 @@ impl Spirv { if all_components_installed { log::debug!("all required components are installed"); } else { - let output = std::process::Command::new("rustup") + let output_component_add = std::process::Command::new("rustup") .args(["component", "add", "--toolchain"]) .arg(&self.channel) .args(["rust-src", "rustc-dev", "llvm-tools"]) @@ -131,7 +152,7 @@ impl Spirv { .output() .unwrap(); assert!( - output.status.success(), + output_component_add.status.success(), "could not install required components" ); } diff --git a/crates/cargo-gpu/src/toml.rs b/crates/cargo-gpu/src/toml.rs index bf3785f..ab360ef 100644 --- a/crates/cargo-gpu/src/toml.rs +++ b/crates/cargo-gpu/src/toml.rs @@ -1,9 +1,12 @@ +//! Build a shader based on the data in the `[package.metadata.rust-gpu.build.spirv-builder]` section of +//! a shader's `Cargo.toml`. use clap::Parser; use crate::{Cli, Command}; +/// `cargo gpu toml` #[derive(Parser)] -pub(crate) struct Toml { +pub struct Toml { /// Path to a workspace or package Cargo.toml file. /// /// Must include a [[workspace | package].metadata.rust-gpu.build] section where @@ -32,6 +35,7 @@ pub(crate) struct Toml { } impl Toml { + /// Entrypoint pub fn run(&self) { // Find the path to the toml file to use let path = if self.path.is_file() && self.path.ends_with(".toml") { @@ -52,17 +56,8 @@ impl Toml { let contents = std::fs::read_to_string(&path).unwrap(); let toml: toml::Table = toml::from_str(&contents).unwrap(); - fn get_metadata_rustgpu_table<'a>( - toml: &'a toml::Table, - toml_type: &'static str, - ) -> Option<&'a toml::Table> { - let workspace = toml.get(toml_type)?.as_table()?; - let metadata = workspace.get("metadata")?.as_table()?; - metadata.get("rust-gpu")?.as_table() - } - let (toml_type, table) = if toml.contains_key("workspace") { - let table = get_metadata_rustgpu_table(&toml, "workspace") + let table = Self::get_metadata_rustgpu_table(&toml, "workspace") .unwrap_or_else(|| { panic!( "toml file '{}' is missing a [workspace.metadata.rust-gpu] table", @@ -72,7 +67,7 @@ impl Toml { .clone(); ("workspace", table) } else if toml.contains_key("package") { - let mut table = get_metadata_rustgpu_table(&toml, "package") + let mut table = Self::get_metadata_rustgpu_table(&toml, "package") .unwrap_or_else(|| { panic!( "toml file '{}' is missing a [package.metadata.rust-gpu] table", @@ -83,7 +78,7 @@ impl Toml { // Ensure the package name is included as the shader-crate parameter if !table.contains_key("shader-crate") { table.insert( - "shader-crate".to_string(), + "shader-crate".to_owned(), format!("{}", path.parent().unwrap().display()).into(), ); } @@ -105,18 +100,19 @@ impl Toml { panic!("toml file's '{toml_type}.metadata.rust-gpu.build' property is not a table") }) .into_iter() - .flat_map(|(k, v)| match v { - toml::Value::String(s) => [format!("--{k}"), s.clone()], - _ => { + .flat_map(|(key, val)| { + if let toml::Value::String(string) = val { + [format!("--{key}"), string.clone()] + } else { let mut value = String::new(); let ser = toml::ser::ValueSerializer::new(&mut value); - serde::Serialize::serialize(v, ser).unwrap(); - [format!("--{k}"), value] + serde::Serialize::serialize(val, ser).unwrap(); + [format!("--{key}"), value] } }) .collect::>(); - parameters.insert(0, "cargo-gpu".to_string()); - parameters.insert(1, "build".to_string()); + parameters.insert(0, "cargo-gpu".to_owned()); + parameters.insert(1, "build".to_owned()); let working_directory = path.parent().unwrap(); log::info!( @@ -128,7 +124,6 @@ impl Toml { log::debug!("build parameters: {parameters:#?}"); if let Cli { command: Command::Build(mut build), - .. } = Cli::parse_from(parameters) { log::debug!("build: {build:?}"); @@ -138,4 +133,14 @@ impl Toml { panic!("could not determin build command"); } } + + /// Parse the `[package.metadata.rust-gpu]` section. + fn get_metadata_rustgpu_table<'toml>( + toml: &'toml toml::Table, + toml_type: &'static str, + ) -> Option<&'toml toml::Table> { + let workspace = toml.get(toml_type)?.as_table()?; + let metadata = workspace.get("metadata")?.as_table()?; + metadata.get("rust-gpu")?.as_table() + } } From 95f248a82c6f1acd17ff57c8ab11699b716a64da Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sun, 15 Dec 2024 13:43:58 +0100 Subject: [PATCH 3/4] Add actual shader compile to smoke tests --- .gitignore | 3 +++ crates/cargo-gpu/src/main.rs | 8 +++++++- crates/shader-crate-template/src/lib.rs | 3 +-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 99d4b40..738d6d1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ test_output cmy_triangle_renderer.svg .aider* flamegraph.svg + +# Compiled shader assets from running tests +crates/shader-crate-template/shaders diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index 3390012..a3d9c86 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -207,11 +207,17 @@ mod test { &format!("{}", output_dir.display()), ]; if let Cli { - command: Command::Build(build), + command: Command::Build(mut build), } = Cli::parse_from(args) { assert_eq!(shader_crate, build.shader_crate); assert_eq!(output_dir, build.output_dir); + + // TODO: + // What's the best way to reset caches for this? For example we could add a + // `--force-spirv-cli-rebuild`, but that would slow down each test. But without + // something like that we might not be getting actual idempotent tests. + build.run(); } else { panic!("was not a build command"); } diff --git a/crates/shader-crate-template/src/lib.rs b/crates/shader-crate-template/src/lib.rs index e98f03d..e12c0ff 100644 --- a/crates/shader-crate-template/src/lib.rs +++ b/crates/shader-crate-template/src/lib.rs @@ -1,7 +1,6 @@ //! Shader entry points. //! -//! Contains an example vertex shader, fragment shader and one example compute -//! shader. +//! Contains an example vertex shader, fragment shader. #![no_std] use glam::{Vec2, Vec4}; use spirv_std::spirv; From f0fc1ea6f0fdbdb3d14daaf77aff8e98cb7ea888 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sun, 15 Dec 2024 22:10:59 +0100 Subject: [PATCH 4/4] Rebase into: Refactor each subcommand to its own file --- crates/cargo-gpu/src/{builder.rs => build.rs} | 0 crates/cargo-gpu/src/main.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/cargo-gpu/src/{builder.rs => build.rs} (100%) diff --git a/crates/cargo-gpu/src/builder.rs b/crates/cargo-gpu/src/build.rs similarity index 100% rename from crates/cargo-gpu/src/builder.rs rename to crates/cargo-gpu/src/build.rs diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index a3d9c86..cb14ee6 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -50,13 +50,13 @@ //! conduct other post-processing, like converting the `spv` files into `wgsl` files, //! for example. -use builder::Build; +use build::Build; use clap::Parser as _; use install::Install; use show::Show; use toml::Toml; -mod builder; +mod build; mod install; mod show; mod spirv;