diff --git a/Cargo.lock b/Cargo.lock index 607acfe..64cedc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bytes" @@ -688,15 +691,27 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags", + "serde", +] + [[package]] name = "spirv-builder-cli" version = "0.1.0" dependencies = [ + "clap", "env_home", "env_logger 0.10.2", "log", "serde", "serde_json", + "spirv", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index a23359e..5a60538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ members = [ ] exclude = [ + # Can't be included because it depends on a specific Rust toolchain and considering that + # the reason it exists is to prevent Rust toolchain requirements from polluting workspaces + # then let's just not try to workaround it. "crates/spirv-builder-cli", # This currently needs to be excluded because it depends on a version of `rust-gpu` that @@ -51,5 +54,4 @@ 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 } diff --git a/README.md b/README.md index ace69f4..992156a 100644 --- a/README.md +++ b/README.md @@ -54,193 +54,195 @@ the usage instructions the backend and nightly Rust version are referred to as " ## Usage +All the following arguments for the `build` and `install` commands can also be set in the shader crate's `Cargo.toml` +file. In general usage that would be the recommended way to set config. See `crates/shader-crate-template/Cargo.toml` +for an example. + ```` -Commands: - install Install rust-gpu compiler artifacts - build Compile a shader crate to SPIR-V - toml Compile a shader crate according to the `cargo gpu build` parameters found in the given toml file - show Show some useful values - help Print this message or the help of the given subcommand(s) + Commands: + install Install rust-gpu compiler artifacts + build Compile a shader crate to SPIR-V + show Show some useful values + help Print this message or the help of the given subcommand(s) -Options: - -h, --help - Print help + Options: + -h, --help + Print help - -V, --version - Print version + -V, --version + Print version * Install + Install rust-gpu compiler artifacts -Install rust-gpu compiler artifacts - -Usage: cargo-gpu install [OPTIONS] + Usage: cargo-gpu install [OPTIONS] -Options: - --shader-crate - Directory containing the shader crate to compile + Options: + --shader-crate + Directory containing the shader crate to compile - [default: ./] + [default: ./] - --spirv-builder-source - Source of `spirv-builder` dependency Eg: "https://github.com/Rust-GPU/rust-gpu" + --spirv-builder-source + Source of `spirv-builder` dependency Eg: "https://github.com/Rust-GPU/rust-gpu" - --spirv-builder-version - Version of `spirv-builder` dependency. - * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic - version such as "0.9.0". - * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such - as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. + --spirv-builder-version + Version of `spirv-builder` dependency. + * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic + version such as "0.9.0". + * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such + as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. - --rust-toolchain - Rust toolchain channel to use to build `spirv-builder`. + --rust-toolchain + Rust toolchain channel to use to build `spirv-builder`. - This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. + This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. - --force-spirv-cli-rebuild - Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt + --force-spirv-cli-rebuild + Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt - --auto-install-rust-toolchain - Assume "yes" to "Install Rust toolchain: [y/n]" prompt + --auto-install-rust-toolchain + Assume "yes" to "Install Rust toolchain: [y/n]" prompt - -h, --help - Print help (see a summary with '-h') + -h, --help + Print help (see a summary with '-h') * Build + Compile a shader crate to SPIR-V -Compile a shader crate to SPIR-V - -Usage: cargo-gpu build [OPTIONS] - -Options: - --shader-crate - Directory containing the shader crate to compile + Usage: cargo-gpu build [OPTIONS] - [default: ./] + Options: + --shader-crate + Directory containing the shader crate to compile - --spirv-builder-source - Source of `spirv-builder` dependency Eg: "https://github.com/Rust-GPU/rust-gpu" + [default: ./] - --spirv-builder-version - Version of `spirv-builder` dependency. - * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic - version such as "0.9.0". - * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such - as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. + --spirv-builder-source + Source of `spirv-builder` dependency Eg: "https://github.com/Rust-GPU/rust-gpu" - --rust-toolchain - Rust toolchain channel to use to build `spirv-builder`. + --spirv-builder-version + Version of `spirv-builder` dependency. + * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic + version such as "0.9.0". + * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such + as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. - This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. + --rust-toolchain + Rust toolchain channel to use to build `spirv-builder`. - --force-spirv-cli-rebuild - Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt + This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. - --auto-install-rust-toolchain - Assume "yes" to "Install Rust toolchain: [y/n]" prompt + --force-spirv-cli-rebuild + Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt - --shader-target - Shader target + --auto-install-rust-toolchain + Assume "yes" to "Install Rust toolchain: [y/n]" prompt - [default: spirv-unknown-vulkan1.2] + -o, --output-dir + Path to the output directory for the compiled shaders - --no-default-features - Set cargo default-features + [default: ./] - --features - Set cargo features + --no-default-features + Set cargo default-features - -o, --output-dir - Path to the output directory for the compiled shaders + --features + Set cargo features - [default: ./] + --target + `rust-gpu` compile target - -h, --help - Print help (see a summary with '-h') + [default: spirv-unknown-vulkan1.2] + --shader-target + Shader target -* Toml + [default: spirv-unknown-vulkan1.2] -Compile a shader crate according to the `cargo gpu build` parameters found in the given toml file + --deny-warnings + Treat warnings as errors during compilation -Usage: cargo-gpu toml [PATH] + --debug + Compile shaders in debug mode -Arguments: - [PATH] - Path to a workspace or package Cargo.toml file. + --capability + Enables the provided SPIR-V capabilities. See: `impl core::str::FromStr for spirv_builder::Capability` - Must include a [[workspace | package].metadata.rust-gpu.build] section where - arguments to `cargo gpu build` are listed. + --extension + Enables the provided SPIR-V extensions. See for all extensions - Path arguments like `output-dir` and `shader-manifest` must be relative to - the location of the Cargo.toml file. + --multimodule + Compile one .spv file per entry point - Example: + --spirv-metadata + Set the level of metadata included in the SPIR-V binary - ```toml - [package.metadata.rust-gpu.build.spirv-builder] - git = "https://github.com/Rust-GPU/rust-gpu.git" - rev = "0da80f8" + [default: none] - [package.metadata.rust-gpu.build] - output-dir = "shaders" - shader-manifest = "shaders/manifest.json" - ``` + --relax-struct-store + Allow store from one struct type to a different type with compatible layout and members - 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". + --relax-logical-pointer + Allow allocating an object of a pointer type and returning a pointer value from a function in logical addressing mode - [default: ./Cargo.toml] + --relax-block-layout + Enable `VK_KHR_relaxed_block_layout` when checking standard uniform, storage buffer, and push constant layouts. This is the default when targeting Vulkan 1.1 or later -Options: - -h, --help - Print help (see a summary with '-h') + --uniform-buffer-standard-layout + Enable `VK_KHR_uniform_buffer_standard_layout` when checking standard uniform buffer layouts + --scalar-block-layout + Enable `VK_EXT_scalar_block_layout` when checking standard uniform, storage buffer, and push constant layouts. Scalar layout rules are more permissive than relaxed block layout so in effect this will override the --relax-block-layout option -* Show - -Show some useful values + --skip-block-layout + Skip checking standard uniform / storage buffer layout. Overrides any --relax-block-layout or --scalar-block-layout option -Usage: cargo-gpu show + --preserve-bindings + Preserve unused descriptor bindings. Useful for reflection -Commands: - cache-directory Displays the location of the cache directory - spirv-source The source location of spirv-std - help Print this message or the help of the given subcommand(s) + -h, --help + Print help (see a summary with '-h') -Options: - -h, --help - Print help +* Show + Show some useful values - * Cache-directory - - Displays the location of the cache directory + Usage: cargo-gpu show - Usage: cargo-gpu show cache-directory + Commands: + cache-directory Displays the location of the cache directory + spirv-source The source location of spirv-std + help Print this message or the help of the given subcommand(s) - Options: - -h, --help - Print help + Options: + -h, --help + Print help - * Spirv-source + * Cache-directory + Displays the location of the cache directory - The source location of spirv-std + Usage: cargo-gpu show cache-directory - Usage: cargo-gpu show spirv-source [OPTIONS] + Options: + -h, --help + Print help - Options: - --shader-crate - The location of the shader-crate to inspect to determine its spirv-std dependency - [default: ./] + * Spirv-source + The source location of spirv-std - -h, --help - Print help + Usage: cargo-gpu show spirv-source [OPTIONS] + Options: + --shader-crate + The location of the shader-crate to inspect to determine its spirv-std dependency + [default: ./] + -h, --help + Print help ```` diff --git a/crates/cargo-gpu/Cargo.toml b/crates/cargo-gpu/Cargo.toml index b05cd68..dc71a7e 100644 --- a/crates/cargo-gpu/Cargo.toml +++ b/crates/cargo-gpu/Cargo.toml @@ -40,6 +40,6 @@ codegen-units = 256 opt-level = 3 incremental = true codegen-units = 256 - + [lints] workspace = true diff --git a/crates/cargo-gpu/src/build.rs b/crates/cargo-gpu/src/build.rs index fce6e36..f16be03 100644 --- a/crates/cargo-gpu/src/build.rs +++ b/crates/cargo-gpu/src/build.rs @@ -1,72 +1,62 @@ //! `cargo gpu build`, analogous to `cargo build` use anyhow::Context as _; -use clap::Parser; -use spirv_builder_cli::{Linkage, ShaderModule}; use crate::{install::Install, target_spec_dir}; +use spirv_builder_cli::{args::BuildArgs, Linkage, ShaderModule}; /// `cargo build` subcommands -#[derive(Parser, Debug)] +#[derive(clap::Parser, Debug, serde::Deserialize, serde::Serialize)] pub struct Build { - /// Install the `rust-gpu` compiler and components + /// CLI args for install the `rust-gpu` compiler and components #[clap(flatten)] pub install: Install, - /// 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, + /// CLI args for configuring the build of the shader + #[clap(flatten)] + pub build: BuildArgs, } impl Build { /// Entrypoint pub fn run(&mut self) -> anyhow::Result<()> { - let (dylib_path, spirv_builder_cli_path) = self.install.run()?; + let 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)?; - let canonicalized = self.output_dir.canonicalize()?; + log::debug!( + "ensuring output-dir '{}' exists", + self.build.output_dir.display() + ); + std::fs::create_dir_all(&self.build.output_dir)?; + let canonicalized = self.build.output_dir.canonicalize()?; log::debug!("canonicalized output dir: {canonicalized:?}"); - self.output_dir = canonicalized; + self.build.output_dir = canonicalized; // Ensure the shader crate exists - self.install.shader_crate = self.install.shader_crate.canonicalize()?; + self.install.spirv_install.shader_crate = + self.install.spirv_install.shader_crate.canonicalize()?; anyhow::ensure!( - self.install.shader_crate.exists(), + self.install.spirv_install.shader_crate.exists(), "shader crate '{}' does not exist. (Current dir is '{}')", - self.install.shader_crate.display(), + self.install.spirv_install.shader_crate.display(), std::env::current_dir()?.display() ); - let spirv_builder_args = spirv_builder_cli::Args { - dylib_path, - shader_crate: self.install.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(), - }; - - let arg = serde_json::to_string_pretty(&spirv_builder_args)?; + self.build.shader_target = target_spec_dir()? + .join(format!("{}.json", self.build.shader_target)) + .display() + .to_string(); + + let args_as_json = serde_json::json!({ + "install": self.install.spirv_install, + "build": self.build + }); + let arg = serde_json::to_string_pretty(&args_as_json)?; log::info!("using spirv-builder-cli arg: {arg}"); crate::user_output!( "Running `spirv-builder-cli` to compile shader at {}...\n", - self.install.shader_crate.display() + self.install.spirv_install.shader_crate.display() ); // Call spirv-builder-cli to compile the shaders. @@ -77,7 +67,7 @@ impl Build { .output()?; anyhow::ensure!(output.status.success(), "build failed"); - let spirv_manifest = self.output_dir.join("spirv-manifest.json"); + let spirv_manifest = self.build.output_dir.join("spirv-manifest.json"); if spirv_manifest.is_file() { log::debug!( "successfully built shaders, raw manifest is at '{}'", @@ -100,21 +90,22 @@ impl Build { }| -> anyhow::Result { use relative_path::PathExt as _; - let path = self.output_dir.join( + let path = self.build.output_dir.join( filepath .file_name() .context("Couldn't parse file name from shader module path")?, ); std::fs::copy(&filepath, &path)?; - let path_relative_to_shader_crate = - path.relative_to(&self.install.shader_crate)?.to_path(""); + let path_relative_to_shader_crate = path + .relative_to(&self.install.spirv_install.shader_crate)? + .to_path(""); Ok(Linkage::new(entry, path_relative_to_shader_crate)) }, ) .collect::>>()?; // Write the shader manifest json file - let manifest_path = self.output_dir.join("manifest.json"); + let manifest_path = self.build.output_dir.join("manifest.json"); // Sort the contents so the output is deterministic linkage.sort(); let json = serde_json::to_string_pretty(&linkage)?; @@ -147,9 +138,9 @@ impl Build { #[cfg(test)] mod test { - use crate::{Cli, Command}; + use clap::Parser as _; - use super::*; + use crate::{Cli, Command}; #[test_log::test] fn builder_from_params() { @@ -170,8 +161,8 @@ mod test { command: Command::Build(build), } = Cli::parse_from(args) { - assert_eq!(shader_crate_path, build.install.shader_crate); - assert_eq!(output_dir, build.output_dir); + assert_eq!(shader_crate_path, build.install.spirv_install.shader_crate); + assert_eq!(output_dir, build.build.output_dir); // TODO: // For some reason running a full build (`build.run()`) inside tests fails on Windows. diff --git a/crates/cargo-gpu/src/config.rs b/crates/cargo-gpu/src/config.rs new file mode 100644 index 0000000..39cb929 --- /dev/null +++ b/crates/cargo-gpu/src/config.rs @@ -0,0 +1,220 @@ +//! Manage and merge the various sources of config: shader crate's `Cargo.toml`(s) and CLI args. +use anyhow::Context as _; +use clap::Parser as _; + +/// Config +pub struct Config; + +impl Config { + /// Get all the config defaults as JSON. + pub fn defaults_as_json() -> anyhow::Result { + let defaults_json = Self::cli_args_to_json(vec![String::new()])?; + Ok(defaults_json) + } + + /// Convert CLI args to their serde JSON representation. + fn cli_args_to_json(env_args: Vec) -> anyhow::Result { + let mut cli_args_json = serde_json::to_value(crate::build::Build::parse_from(env_args))?; + + // Move `/install/spirv_install` to `/install` + let spirv_install = cli_args_json + .pointer("/install/spirv_install") + .context("`/install/spirv_install` not found in config")? + .clone(); + *cli_args_json + .get_mut("install") + .context("`/install` not found in config")? = spirv_install; + + Ok(cli_args_json) + } + + /// Config for the `cargo gpu build` and `cargo gpu install` can be set in the shader crate's + /// `Cargo.toml`, so here we load that config first as the base config, and the CLI arguments can + /// then later override it. + pub fn clap_command_with_cargo_config( + shader_crate_path: &std::path::PathBuf, + mut env_args: Vec, + ) -> anyhow::Result { + let mut config = crate::metadata::Metadata::as_json(shader_crate_path)?; + + env_args = env_args + .into_iter() + .filter(|arg| !(arg == "build" || arg == "install")) + .collect::>(); + let cli_args_json = Self::cli_args_to_json(env_args)?; + + Self::json_merge(&mut config, cli_args_json, None)?; + + let build = config + .get("build") + .context("`build` not found in merged configs")? + .clone(); + + let install = config + .get("install") + .context("`install` not found in merged configs")? + .clone(); + + let args = serde_json::from_value::(serde_json::json!({ + "build": build, + "install": { + "spirv_install": install + } + }))?; + Ok(args) + } + + /// Merge 2 JSON objects. But only if the incoming patch value isn't the default value. + /// Inspired by: + pub fn json_merge( + left_in: &mut serde_json::Value, + right_in: serde_json::Value, + maybe_pointer: Option<&String>, + ) -> anyhow::Result<()> { + let defaults = Self::defaults_as_json()?; + + match (left_in, right_in) { + (left @ &mut serde_json::Value::Object(_), serde_json::Value::Object(right)) => { + let left_as_object = left + .as_object_mut() + .context("Unreachable, we've already proved it's an object")?; + for (key, value) in right { + let new_pointer = maybe_pointer.as_ref().map_or_else( + || format!("/{}", key.clone()), + |pointer| format!("{}/{}", pointer, key.clone()), + ); + Self::json_merge( + left_as_object + .entry(key.clone()) + .or_insert(serde_json::Value::Null), + value, + Some(&new_pointer), + )?; + } + } + (left, right) => { + if let Some(pointer) = maybe_pointer { + let default = defaults + .pointer(pointer) + .context(format!("Config `{pointer}` not found in defaults"))?; + if &right != default { + // Only overwrite if the new value differs from the defaults. + *left = right; + } + } + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::io::Write as _; + + #[test_log::test] + fn booleans_from_cli() { + let shader_crate_path = crate::test::shader_crate_test_path(); + + let args = Config::clap_command_with_cargo_config( + &shader_crate_path, + vec![ + "gpu".to_owned(), + "build".to_owned(), + "--debug".to_owned(), + "--auto-install-rust-toolchain".to_owned(), + ], + ) + .unwrap(); + assert!(args.build.debug); + assert!(args.install.spirv_install.auto_install_rust_toolchain); + } + + #[test_log::test] + fn booleans_from_cargo() { + let shader_crate_path = crate::test::shader_crate_test_path(); + let mut file = crate::test::overwrite_shader_cargo_toml(&shader_crate_path); + file.write_all( + [ + "[package.metadata.rust-gpu.build]", + "debug = true", + "[package.metadata.rust-gpu.install]", + "auto-install-rust-toolchain = true", + ] + .join("\n") + .as_bytes(), + ) + .unwrap(); + + let args = Config::clap_command_with_cargo_config(&shader_crate_path, vec![]).unwrap(); + assert!(args.build.debug); + assert!(args.install.spirv_install.auto_install_rust_toolchain); + } + + fn update_cargo_output_dir() -> std::path::PathBuf { + let shader_crate_path = crate::test::shader_crate_test_path(); + let mut file = crate::test::overwrite_shader_cargo_toml(&shader_crate_path); + file.write_all( + [ + "[package.metadata.rust-gpu.build]", + "output-dir = \"/the/moon\"", + ] + .join("\n") + .as_bytes(), + ) + .unwrap(); + shader_crate_path + } + + #[test_log::test] + fn string_from_cargo() { + let shader_crate_path = update_cargo_output_dir(); + + let args = Config::clap_command_with_cargo_config(&shader_crate_path, vec![]).unwrap(); + assert_eq!(args.build.output_dir, std::path::Path::new("/the/moon")); + } + + #[test_log::test] + fn string_from_cargo_overwritten_by_cli() { + let shader_crate_path = update_cargo_output_dir(); + + let args = Config::clap_command_with_cargo_config( + &shader_crate_path, + vec![ + "gpu".to_owned(), + "build".to_owned(), + "--output-dir".to_owned(), + "/the/river".to_owned(), + ], + ) + .unwrap(); + assert_eq!(args.build.output_dir, std::path::Path::new("/the/river")); + } + + #[test_log::test] + fn arrays_from_cargo() { + let shader_crate_path = crate::test::shader_crate_test_path(); + let mut file = crate::test::overwrite_shader_cargo_toml(&shader_crate_path); + file.write_all( + [ + "[package.metadata.rust-gpu.build]", + "capability = [\"AtomicStorage\", \"Matrix\"]", + ] + .join("\n") + .as_bytes(), + ) + .unwrap(); + + let args = Config::clap_command_with_cargo_config(&shader_crate_path, vec![]).unwrap(); + assert_eq!( + args.build.capability, + vec![ + spirv_builder_cli::spirv::Capability::AtomicStorage, + spirv_builder_cli::spirv::Capability::Matrix + ] + ); + } +} diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index c6e3eef..aafc969 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -4,6 +4,7 @@ use std::io::Write as _; use anyhow::Context as _; use crate::{cache_dir, spirv_cli::SpirvCli, spirv_source::SpirvSource, target_spec_dir}; +use spirv_builder_cli::args::InstallArgs; /// These are the files needed to create the dedicated, per-shader `rust-gpu` builder create. const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[ @@ -19,6 +20,10 @@ const SPIRV_BUILDER_FILES: &[(&str, &str)] = &[ "src/lib.rs", include_str!("../../spirv-builder-cli/src/lib.rs"), ), + ( + "src/args.rs", + include_str!("../../spirv-builder-cli/src/args.rs"), + ), ]; /// Metadata for the compile targets supported by `rust-gpu` @@ -86,42 +91,11 @@ const TARGET_SPECS: &[(&str, &str)] = &[ ]; /// `cargo gpu install` -#[derive(clap::Parser, Debug)] +#[derive(clap::Parser, Debug, serde::Deserialize, serde::Serialize)] pub struct Install { - /// Directory containing the shader crate to compile. - #[clap(long, default_value = "./")] - pub shader_crate: std::path::PathBuf, - - #[expect( - clippy::doc_markdown, - reason = "The URL should appear literally like this. But Clippy wants it to be a in markdown clickable link" - )] - /// Source of `spirv-builder` dependency - /// Eg: "https://github.com/Rust-GPU/rust-gpu" - #[clap(long)] - spirv_builder_source: Option, - - /// Version of `spirv-builder` dependency. - /// * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic - /// version such as "0.9.0". - /// * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such - /// as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. - #[clap(long, verbatim_doc_comment)] - spirv_builder_version: Option, - - /// Rust toolchain channel to use to build `spirv-builder`. - /// - /// This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. - #[clap(long)] - rust_toolchain: Option, - - /// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. - #[clap(long)] - force_spirv_cli_rebuild: bool, - - /// Assume "yes" to "Install Rust toolchain: [y/n]" prompt. - #[clap(long, action)] - auto_install_rust_toolchain: bool, + /// CLI arguments for installing the Rust toolchain and components + #[clap(flatten)] + pub spirv_install: InstallArgs, } impl Install { @@ -129,16 +103,16 @@ impl Install { fn spirv_cli(&self, shader_crate_path: &std::path::PathBuf) -> anyhow::Result { SpirvCli::new( shader_crate_path, - self.spirv_builder_source.clone(), - self.spirv_builder_version.clone(), - self.rust_toolchain.clone(), - self.auto_install_rust_toolchain, + self.spirv_install.spirv_builder_source.clone(), + self.spirv_install.spirv_builder_version.clone(), + self.spirv_install.rust_toolchain.clone(), + self.spirv_install.auto_install_rust_toolchain, ) } /// Create the `spirv-builder-cli` crate. fn write_source_files(&self) -> anyhow::Result<()> { - let spirv_cli = self.spirv_cli(&self.shader_crate)?; + let spirv_cli = self.spirv_cli(&self.spirv_install.shader_crate)?; let checkout = spirv_cli.cached_checkout_path()?; std::fs::create_dir_all(checkout.join("src"))?; for (filename, contents) in SPIRV_BUILDER_FILES { @@ -187,7 +161,7 @@ impl Install { fn write_target_spec_files(&self) -> anyhow::Result<()> { for (filename, contents) in TARGET_SPECS { let path = target_spec_dir()?.join(filename); - if !path.is_file() || self.force_spirv_cli_rebuild { + if !path.is_file() || self.spirv_install.force_spirv_cli_rebuild { let mut file = std::fs::File::create(&path)?; file.write_all(contents.as_bytes())?; } @@ -196,7 +170,7 @@ impl Install { } /// Install the binary pair and return the paths, (dylib, cli). - pub fn run(&self) -> anyhow::Result<(std::path::PathBuf, std::path::PathBuf)> { + pub fn run(&mut self) -> anyhow::Result { // Ensure the cache dir exists let cache_dir = cache_dir()?; log::info!("cache directory is '{}'", cache_dir.display()); @@ -204,7 +178,7 @@ impl Install { format!("could not create cache directory '{}'", cache_dir.display()) })?; - let spirv_version = self.spirv_cli(&self.shader_crate)?; + let spirv_version = self.spirv_cli(&self.spirv_install.shader_crate)?; spirv_version.ensure_toolchain_and_components_exist()?; let checkout = spirv_version.cached_checkout_path()?; @@ -225,7 +199,10 @@ impl Install { ); } - if dest_dylib_path.is_file() && dest_cli_path.is_file() && !self.force_spirv_cli_rebuild { + if dest_dylib_path.is_file() + && dest_cli_path.is_file() + && !self.spirv_install.force_spirv_cli_rebuild + { log::info!("...and so we are aborting the install step."); } else { log::debug!( @@ -237,7 +214,7 @@ impl Install { crate::user_output!( "Compiling shader-specific `spirv-builder-cli` for {}\n", - self.shader_crate.display() + self.spirv_install.shader_crate.display() ); let mut command = std::process::Command::new("cargo"); @@ -286,7 +263,10 @@ impl Install { anyhow::bail!("spirv-builder-cli build failed"); } } - Ok((dest_dylib_path, dest_cli_path)) + + self.spirv_install.dylib_path = dest_dylib_path; + + Ok(dest_cli_path) } /// The `spirv-builder` crate from the main `rust-gpu` repo hasn't always been setup to diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index c180973..d3f906e 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -56,14 +56,14 @@ use build::Build; use clap::Parser as _; use install::Install; use show::Show; -use toml::Toml; mod build; +mod config; mod install; +mod metadata; mod show; mod spirv_cli; mod spirv_source; -mod toml; /// Central function to write to the user. #[macro_export] @@ -115,28 +115,33 @@ fn main() { /// Wrappable "main" to catch errors. fn run() -> anyhow::Result<()> { - let args = std::env::args() + let env_args = std::env::args() .filter(|arg| { - // Calling cargo-gpu as the cargo subcommand "cargo gpu" passes "gpu" - // as the first parameter, which we want to ignore. + // Calling our `main()` with the cargo subcommand `cargo gpu` passes "gpu" + // as the first parameter, so we want to ignore it. arg != "gpu" }) .collect::>(); - log::trace!("args: {args:?}"); - let cli = Cli::parse_from(args); + log::trace!("CLI args: {env_args:#?}"); + let cli = Cli::parse_from(env_args.clone()); match cli.command { Command::Install(install) => { - log::debug!("installing with arguments: {install:#?}"); - let (_, _) = install.run()?; + let shader_crate_path = install.spirv_install.shader_crate; + let mut command = + config::Config::clap_command_with_cargo_config(&shader_crate_path, env_args)?; + log::debug!( + "installing with final merged arguments: {:#?}", + command.install + ); + let _: std::path::PathBuf = command.install.run()?; } - Command::Build(mut build) => { - log::debug!("building with arguments: {build:#?}"); - build.run()?; - } - Command::Toml(toml) => { - log::debug!("building by toml file with arguments: {toml:#?}"); - toml.run()?; + Command::Build(build) => { + let shader_crate_path = build.install.spirv_install.shader_crate; + let mut command = + config::Config::clap_command_with_cargo_config(&shader_crate_path, env_args)?; + log::debug!("building with final merged arguments: {command:#?}"); + command.run()?; } Command::Show(show) => show.run()?, Command::DumpUsage => dump_full_usage_for_readme()?, @@ -154,10 +159,6 @@ enum Command { /// Compile a shader crate to SPIR-V. Build(Build), - /// Compile a shader crate according to the `cargo gpu build` parameters - /// found in the given toml file. - Toml(Toml), - /// Show some useful values. Show(Show), @@ -217,26 +218,30 @@ fn dump_full_usage_for_readme() -> anyhow::Result<()> { fn write_help( buffer: &mut impl std::io::Write, cmd: &mut clap::Command, - _depth: usize, + depth: usize, ) -> anyhow::Result<()> { if cmd.get_name() == "help" { return Ok(()); } let mut command = cmd.get_name().to_owned(); + let indent_depth = if depth == 0 || depth == 1 { 0 } else { depth }; + let indent = " ".repeat(indent_depth * 4); writeln!( buffer, - "\n* {}{}", + "\n{}* {}{}", + indent, command.remove(0).to_uppercase(), command )?; - writeln!(buffer)?; - cmd.write_long_help(buffer)?; + + for line in cmd.render_long_help().to_string().lines() { + writeln!(buffer, "{indent} {line}")?; + } for sub in cmd.get_subcommands_mut() { writeln!(buffer)?; - #[expect(clippy::used_underscore_binding, reason = "Used in recursion only")] - write_help(buffer, sub, _depth + 1)?; + write_help(buffer, sub, depth + 1)?; } Ok(()) @@ -258,12 +263,31 @@ fn to_dirname(text: &str) -> String { #[cfg(test)] mod test { use crate::cache_dir; + use std::io::Write as _; pub fn shader_crate_template_path() -> std::path::PathBuf { let project_base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); project_base.join("../shader-crate-template") } + pub fn shader_crate_test_path() -> std::path::PathBuf { + let shader_crate_path = crate::cache_dir().unwrap().join("shader_crate"); + copy_dir_all(shader_crate_template_path(), shader_crate_path.clone()).unwrap(); + shader_crate_path + } + + pub fn overwrite_shader_cargo_toml(shader_crate_path: &std::path::Path) -> std::fs::File { + let cargo_toml = shader_crate_path.join("Cargo.toml"); + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(cargo_toml) + .unwrap(); + writeln!(file, "[package]").unwrap(); + writeln!(file, "name = \"test\"").unwrap(); + file + } + pub fn tests_teardown() { let cache_dir = cache_dir().unwrap(); if !cache_dir.exists() { @@ -271,4 +295,21 @@ mod test { } std::fs::remove_dir_all(cache_dir).unwrap(); } + + pub fn copy_dir_all( + src: impl AsRef, + dst: impl AsRef, + ) -> anyhow::Result<()> { + std::fs::create_dir_all(&dst)?; + for maybe_entry in std::fs::read_dir(src)? { + let entry = maybe_entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) + } } diff --git a/crates/cargo-gpu/src/metadata.rs b/crates/cargo-gpu/src/metadata.rs new file mode 100644 index 0000000..abc16da --- /dev/null +++ b/crates/cargo-gpu/src/metadata.rs @@ -0,0 +1,197 @@ +//! Get config from the shader crate's `Cargo.toml` `[*.metadata.rust-gpu.*]` + +/// `Metadata` refers to the `[metadata.*]` section of `Cargo.toml` that `cargo` formally +/// ignores so that packages can implement their own behaviour with it. +#[derive(Debug)] +pub struct Metadata; + +impl Metadata { + /// Convert `rust-gpu`-specific sections in `Cargo.toml` to `clap`-compatible arguments. + /// The section in question is: `[package.metadata.rust-gpu.*]`. See the `shader-crate-template` + /// for an example. + /// + /// First we generate the CLI arg defaults as JSON. Then on top of those we merge any config + /// from the workspace `Cargo.toml`, then on top of those we merge any config from the shader + /// crate's `Cargo.toml`. + pub fn as_json(path: &std::path::PathBuf) -> anyhow::Result { + let cargo_json = Self::get_cargo_toml_as_json(path)?; + let config = Self::merge_configs(&cargo_json, path)?; + Ok(config) + } + + /// Convert JSON keys from kebab case to snake case. Eg: `a-b` to `a_b`. + /// + /// Detection of keys for serde deserialization must match the case in the Rust structs. + /// However clap defaults to detecting CLI args in kebab case. So here we do the conversion. + fn keys_to_snake_case(json: &mut serde_json::Value) { + let serde_json::Value::Object(object) = json else { + return; + }; + + *object = core::mem::take(object) + .into_iter() + .map(|(key, mut value)| { + if let serde_json::Value::Object(_) = value { + Self::keys_to_snake_case(&mut value); + } + (key.replace('-', "_"), value) + }) + .collect(); + } + + /// Merge the various source of config: defaults, workspace and shader crate. + fn merge_configs( + cargo_json: &serde_json::Value, + path: &std::path::Path, + ) -> anyhow::Result { + let mut metadata = crate::config::Config::defaults_as_json()?; + crate::config::Config::json_merge( + &mut metadata, + Self::get_workspace_metadata(cargo_json), + None, + )?; + crate::config::Config::json_merge( + &mut metadata, + Self::get_crate_metadata(cargo_json, path)?, + None, + )?; + + Ok(metadata) + } + + /// Convert a `Cargo.toml` to JSON + // + // TODO: reuse for getting the default `rust-gpu` source and toolchain. + fn get_cargo_toml_as_json(path: &std::path::PathBuf) -> anyhow::Result { + let cargo_toml_path = path.join("Cargo.toml"); + if !cargo_toml_path.exists() { + anyhow::bail!("{path:?} must be a shader crate directory"); + } + + log::debug!("Querying Cargo metadata for {cargo_toml_path:?}"); + let output_cargo = std::process::Command::new("cargo") + .args([ + "metadata", + "--no-deps", + "--manifest-path", + cargo_toml_path.display().to_string().as_ref(), + ]) + .output()?; + anyhow::ensure!( + output_cargo.status.success(), + "could not run `cargo metadata` on {cargo_toml_path:?}" + ); + + Ok(serde_json::from_slice(&output_cargo.stdout)?) + } + + /// Get any `rust-gpu` metadata set in the root workspace `Cargo.toml` + fn get_workspace_metadata(json: &serde_json::Value) -> serde_json::Value { + let empty_json_object = serde_json::json!({}); + let mut metadata = json + .pointer("/metadata/rust-gpu") + .unwrap_or(&empty_json_object) + .clone(); + + Self::keys_to_snake_case(&mut metadata); + metadata.clone() + } + + /// Get any `rust-gpu` metadata set in the crate's `Cargo.toml` + fn get_crate_metadata( + json: &serde_json::Value, + path: &std::path::Path, + ) -> anyhow::Result { + let empty_json_object = serde_json::json!({}); + if let Some(serde_json::Value::Array(packages)) = json.pointer("/packages") { + for package in packages { + if let Some(serde_json::Value::String(manifest_path_dirty)) = + package.pointer("/manifest_path") + { + let mut shader_crate_path = std::fs::canonicalize(path)? + .join("Cargo.toml") + .display() + .to_string(); + + // Windows prefixs paths with `\\?\` + shader_crate_path = shader_crate_path.replace(r"\\?\", ""); + let manifest_path = manifest_path_dirty.replace(r"\\?\", ""); + log::debug!("Matching shader crate path with manifest path: {shader_crate_path} == {manifest_path}?"); + if manifest_path == shader_crate_path { + let mut metadata = package + .pointer("/metadata/rust-gpu") + .unwrap_or(&empty_json_object) + .clone(); + Self::keys_to_snake_case(&mut metadata); + return Ok(metadata); + } + } + } + } + Ok(empty_json_object) + } +} + +#[expect( + clippy::indexing_slicing, + reason = "We don't need to be so strict in tests" +)] +#[cfg(test)] +mod test { + use super::*; + + #[test_log::test] + fn generates_defaults() { + let json = serde_json::json!({}); + let configs = Metadata::merge_configs(&json, std::path::Path::new("./")).unwrap(); + assert_eq!(configs["build"]["debug"], serde_json::Value::Bool(false)); + assert_eq!( + configs["install"]["auto_install_rust_toolchain"], + serde_json::Value::Bool(false) + ); + } + + #[test_log::test] + fn can_override_config_from_workspace_toml() { + let json = serde_json::json!( + { "metadata": { "rust-gpu": { + "build": { + "debug": true + }, + "install": { + "auto-install-rust-toolchain": true + } + }}} + ); + let configs = Metadata::merge_configs(&json, std::path::Path::new("./")).unwrap(); + assert_eq!(configs["build"]["debug"], serde_json::Value::Bool(true)); + assert_eq!( + configs["install"]["auto_install_rust_toolchain"], + serde_json::Value::Bool(true) + ); + } + + #[test_log::test] + fn can_override_config_from_crate_toml() { + let marker = std::path::Path::new("./Cargo.toml"); + let json = serde_json::json!( + { "packages": [{ + "metadata": { "rust-gpu": { + "build": { + "debug": true + }, + "install": { + "auto-install-rust-toolchain": true + } + }}, + "manifest_path": std::fs::canonicalize(marker).unwrap() + }]} + ); + let configs = Metadata::merge_configs(&json, marker.parent().unwrap()).unwrap(); + assert_eq!(configs["build"]["debug"], serde_json::Value::Bool(true)); + assert_eq!( + configs["install"]["auto_install_rust_toolchain"], + serde_json::Value::Bool(true) + ); + } +} diff --git a/crates/cargo-gpu/src/spirv_cli.rs b/crates/cargo-gpu/src/spirv_cli.rs index 4e07a73..38dfb86 100644 --- a/crates/cargo-gpu/src/spirv_cli.rs +++ b/crates/cargo-gpu/src/spirv_cli.rs @@ -105,7 +105,7 @@ impl SpirvCli { } else { let message = format!("Rust {} with `rustup`", self.channel); self.get_consent_for_toolchain_install(format!("Install {message}").as_ref())?; - crate::user_output!("Installing {message}"); + crate::user_output!("Installing {message}\n"); let output_toolchain_add = std::process::Command::new("rustup") .args(["toolchain", "add"]) @@ -143,7 +143,7 @@ impl SpirvCli { } else { let message = "toolchain components (rust-src, rustc-dev, llvm-tools) with `rustup`"; self.get_consent_for_toolchain_install(format!("Install {message}").as_ref())?; - crate::user_output!("Installing {message}"); + crate::user_output!("Installing {message}\n"); let output_component_add = std::process::Command::new("rustup") .args(["component", "add", "--toolchain"]) @@ -193,6 +193,8 @@ mod test { #[test_log::test] fn cached_checkout_dir_sanity() { let shader_template_path = crate::test::shader_crate_template_path(); + // TODO: This downloads the `rust-gpu` repo which slows the test down. Can we avoid that + // just to get the sanity check? let spirv = SpirvCli::new(&shader_template_path, None, None, None, true).unwrap(); let dir = spirv.cached_checkout_path().unwrap(); let name = dir diff --git a/crates/cargo-gpu/src/spirv_source.rs b/crates/cargo-gpu/src/spirv_source.rs index d98fde9..498e20e 100644 --- a/crates/cargo-gpu/src/spirv_source.rs +++ b/crates/cargo-gpu/src/spirv_source.rs @@ -320,7 +320,7 @@ impl SpirvSource { self.to_dirname()?.to_string_lossy().as_ref(), ); - crate::user_output!("Cloning `rust-gpu` repo..."); + crate::user_output!("Cloning `rust-gpu` repo...\n"); let output_clone = std::process::Command::new("git") .args([ diff --git a/crates/cargo-gpu/src/toml.rs b/crates/cargo-gpu/src/toml.rs deleted file mode 100644 index c431467..0000000 --- a/crates/cargo-gpu/src/toml.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Build a shader based on the data in the `[package.metadata.rust-gpu.build.spirv-builder]` section of -//! a shader's `Cargo.toml`. - -use anyhow::Context as _; -use clap::Parser; - -use crate::{Cli, Command}; - -/// `cargo gpu toml` -#[derive(Debug, Parser)] -pub 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 { - /// Entrypoint - pub fn run(&self) -> anyhow::Result<()> { - let (path, toml) = Self::parse_cargo_toml(self.path.clone())?; - let working_directory = path - .parent() - .context("Couldn't find parent for shader's `Cargo.toml`")?; - - // Determine if this is a workspace's Cargo.toml or a crate's Cargo.toml - let (toml_type, table) = if toml.contains_key("workspace") { - let table = Self::get_metadata_rustgpu_table(&toml, "workspace") - .with_context(|| { - format!( - "toml file '{}' is missing a [workspace.metadata.rust-gpu] table", - path.display() - ) - })? - .clone(); - ("workspace", table) - } else if toml.contains_key("package") { - let mut table = Self::get_metadata_rustgpu_table(&toml, "package") - .with_context(|| { - format!( - "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_owned(), - format!("{}", working_directory.display()).into(), - ); - } - ("package", table) - } else { - anyhow::bail!("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:#?}"); - - log::info!( - "issuing cargo commands from the working directory '{}'", - working_directory.display() - ); - std::env::set_current_dir(working_directory)?; - - let parameters = construct_build_parameters_from_toml_table(toml_type, &table)?; - 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`"); - anyhow::bail!("could not determin build command"); - } - - Ok(()) - } - - /// Parse the contents of the shader's `Cargo.toml` - pub fn parse_cargo_toml( - mut path: std::path::PathBuf, - ) -> anyhow::Result<(std::path::PathBuf, toml::Table)> { - // Find the path to the toml file to use - let parsed_path = if path.is_file() && path.ends_with(".toml") { - path - } else { - path = path.join("Cargo.toml"); - if path.is_file() { - path - } else { - log::error!("toml file '{}' is not a file", path.display()); - anyhow::bail!("toml file '{}' is not a file", path.display()); - } - }; - - log::info!("using toml file '{}'", parsed_path.display()); - - let contents = std::fs::read_to_string(&parsed_path)?; - let toml: toml::Table = toml::from_str(&contents)?; - - Ok((parsed_path, toml)) - } - - /// 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() - } -} - -/// Construct the cli parameters to run a `cargo gpu build` command from a TOML table. -fn construct_build_parameters_from_toml_table( - toml_type: &str, - table: &toml::map::Map, -) -> Result, anyhow::Error> { - let build_table = table - .get("build") - .with_context(|| "toml is missing the 'build' table")? - .as_table() - .with_context(|| { - format!("toml file's '{toml_type}.metadata.rust-gpu.build' property is not a table") - })?; - let mut parameters: Vec = build_table - .into_iter() - .map(|(key, val)| -> anyhow::Result> { - Ok(match val { - toml::Value::String(string) => vec![format!("--{key}"), string.clone()], - toml::Value::Boolean(truthy) => { - if *truthy { - vec![format!("--{key}")] - } else { - vec![] - } - } - toml::Value::Integer(_) - | toml::Value::Float(_) - | toml::Value::Datetime(_) - | toml::Value::Array(_) - | toml::Value::Table(_) => { - let mut value = String::new(); - let ser = toml::ser::ValueSerializer::new(&mut value); - serde::Serialize::serialize(val, ser)?; - vec![format!("--{key}"), value] - } - }) - }) - .collect::>>>()? - .into_iter() - .flatten() - .collect(); - parameters.insert(0, "cargo-gpu".to_owned()); - parameters.insert(1, "build".to_owned()); - Ok(parameters) -} diff --git a/crates/shader-crate-template/Cargo.toml b/crates/shader-crate-template/Cargo.toml index 3ec4841..d684074 100644 --- a/crates/shader-crate-template/Cargo.toml +++ b/crates/shader-crate-template/Cargo.toml @@ -2,7 +2,7 @@ name = "rust-gpu-shader-crate-template" version = "0.1.0" edition = "2021" - + [lib] crate-type = ["rlib", "cdylib"] @@ -15,8 +15,77 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "82a0f69" } [target.'cfg(target_arch = "spirv")'.dependencies] glam = { version = "0.29", default-features = false, features = ["libm"] } - # Dependencies for CPU code [target.'cfg(not(target_arch = "spirv"))'.dependencies] glam = { version = "0.29", features = ["std"] } +[package.metadata.rust-gpu.build] +# Where to output the compiled shader. Defaults to where `cargo gpu` is called from. +# TODO: Should it default to the root of the shader crate? +output-dir = "./" +# Set shader crate's cargo default-features +no_default_features = false +# Set shader crate's cargo features. +features = [] +# The compile target. +# TODO: `cargo gpu show targets` for all available options. +shader-target = "spirv-unknown-vulkan1.2" +# Treat warnings as errors during compilation. +deny-warnings = false +# Compile shaders in debug mode. +debug = false +# Enables the provided SPIR-V capabilities. +# See: `impl core::str::FromStr for spirv_builder::Capability`. +# TODO: `cargo gpu show capabilities` for all available options. +capability = [] +# Enables the provided SPIR-V extensions. +# See https://github.com/KhronosGroup/SPIRV-Registry for all extensions +# TODO: `cargo gpu show extensions` for all available options. +extension = [] +# Compile one .spv file per shader entry point. +multimodule = false +# Set the level of metadata included in the SPIR-V binary. +# Options: "None", "NameVariables", "Full". +spirv-metadata = "None" +# Allow store from one struct type to a different type with compatible layout and members. +relax-struct-store = false +# Allow allocating an object of a pointer type and returning a pointer value from a function +# in logical addressing mode. +relax-logical-pointer = false +# Enable VK_KHR_relaxed_block_layout when checking standard uniform, storage buffer, and push +# constant layouts. +# This is the default when targeting Vulkan 1.1 or later. +relax-block-layout = false +# Enable VK_KHR_uniform_buffer_standard_layout when checking standard uniform buffer layouts. +uniform-buffer-standard-layout = false +# Enable `VK_EXT_scalar_block_layout` when checking standard uniform, storage buffer, and push +# constant layouts. +# Scalar layout rules are more permissive than relaxed block layout so in effect this will +# override the `relax_block_layout` option. +scalar-block-layout = false +# Skip checking standard uniform/storage buffer layout. +# Overrides `relax_block_layout` and `scalar_block_layout`. +skip-block-layout = false +# Preserve unused descriptor bindings. Useful for reflection. +preserve-bindings = false + +[package.metadata.rust-gpu.install] +# Source of `spirv-builder` dependency +# Eg: "https://github.com/Rust-GPU/rust-gpu" +# spirv_builder_source = "" + +# Version of `spirv-builder` dependency. +# * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic +# version such as "0.9.0". +# * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such +# as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. +# spirv_builder_version = "" + +# Rust toolchain channel to use to build `spirv-builder`. +# Eg: "nightly-2024-04-24" +# rust_toolchain = "" + +# Whether to assume "yes" to the "Install Rust toolchain: [y/n]" prompt. +auto-install-rust-toolchain = false +# Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. +force-spirv-cli-rebuild = false diff --git a/crates/spirv-builder-cli/Cargo.lock b/crates/spirv-builder-cli/Cargo.lock index 683b299..b86b9a2 100644 --- a/crates/spirv-builder-cli/Cargo.lock +++ b/crates/spirv-builder-cli/Cargo.lock @@ -22,6 +22,55 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "ar" version = "0.9.0" @@ -51,6 +100,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bytemuck" @@ -81,6 +133,52 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "convert_case" version = "0.4.0" @@ -182,6 +280,12 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -231,6 +335,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -603,6 +713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ "bitflags 2.6.0", + "serde", ] [[package]] @@ -635,11 +746,13 @@ dependencies = [ name = "spirv-builder-cli" version = "0.1.0" dependencies = [ + "clap", "env_home", "env_logger", "log", "serde", "serde_json", + "spirv 0.3.0+sdk-1.3.268.0", "spirv-builder 0.9.0 (git+https://github.com/Rust-GPU/rust-gpu?rev=4c633aec)", "spirv-builder 0.9.0 (git+https://github.com/Rust-GPU/rust-gpu?rev=60dcb82)", "toml", @@ -669,6 +782,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -740,6 +859,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" diff --git a/crates/spirv-builder-cli/Cargo.toml b/crates/spirv-builder-cli/Cargo.toml index 6723c5b..2ab9ccc 100644 --- a/crates/spirv-builder-cli/Cargo.toml +++ b/crates/spirv-builder-cli/Cargo.toml @@ -3,19 +3,14 @@ name = "spirv-builder-cli" version = "0.1.0" edition = "2021" -[lib] -path = "src/lib.rs" - -[[bin]] -name = "spirv-builder-cli" -path = "src/main.rs" - [dependencies] +clap = { version = "4.4.8", features = ["derive"] } env_home = "0.1.0" env_logger = "0.10" log = "0.4" serde = "1.0.214" serde_json = "1.0.132" +spirv = { version = "0.3.0", features = [ "deserialize", "serialize" ] } toml = "0.8.19" [features] @@ -41,7 +36,7 @@ package = "spirv-builder" optional = true git = "https://github.com/Rust-GPU/rust-gpu" # ${AUTO-REPLACE-SOURCE} rev = "60dcb82" # ${AUTO-REPLACE-VERSION} - + [lints.rust] # This crate is most often run by end users compiling their shaders so it's not so relevant # for them to see warnings. diff --git a/crates/spirv-builder-cli/src/args.rs b/crates/spirv-builder-cli/src/args.rs new file mode 100644 index 0000000..853c062 --- /dev/null +++ b/crates/spirv-builder-cli/src/args.rs @@ -0,0 +1,165 @@ +use std::str::FromStr as _; + +#[derive(clap::Parser, Debug, serde::Deserialize, serde::Serialize)] +pub struct AllArgs { + #[clap(flatten)] + pub build: BuildArgs, + + #[clap(flatten)] + pub install: InstallArgs, +} + +/// Options for the `--spirv-metadata` command +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum SpirvMetadata { + /// Don't log any metadata (the default) + None, + /// Only log named variables + NameVariables, + /// Log all metadata + Full, +} + +#[derive(clap::Parser, Debug, serde::Deserialize, serde::Serialize)] +pub struct BuildArgs { + /// Path to the output directory for the compiled shaders. + #[clap(long, short, default_value = "./")] + pub output_dir: std::path::PathBuf, + + /// Set shader crate's cargo default-features. + #[clap(long)] + pub no_default_features: bool, + + /// Set shader crate's cargo features. + #[clap(long)] + pub features: Vec, + + /// `rust-gpu` compile target. + /// TODO: deprecate completely + #[arg(hide(true), default_value = "spirv-unknown-vulkan1.2")] + pub target: String, + + /// Shader target. + // TODO: how to list the available options? Would be nice to have a command like: + // `cargo gpu show targets` + #[clap(long, default_value = "spirv-unknown-vulkan1.2")] + pub shader_target: String, + + /// Treat warnings as errors during compilation. + #[arg(long, default_value = "false")] + pub deny_warnings: bool, + + /// Compile shaders in debug mode. + #[arg(long, default_value = "false")] + pub debug: bool, + + /// Enables the provided SPIR-V capabilities. + /// See: `impl core::str::FromStr for spirv_builder::Capability` + #[arg(long, value_parser=Self::spirv_capability)] + pub capability: Vec, + + /// Enables the provided SPIR-V extensions. + /// See for all extensions + #[arg(long)] + pub extension: Vec, + + /// Compile one .spv file per entry point. + #[arg(long, default_value = "false")] + pub multimodule: bool, + + /// Set the level of metadata included in the SPIR-V binary. + #[arg(long, value_parser=Self::spirv_metadata, default_value = "none")] + pub spirv_metadata: SpirvMetadata, + + /// Allow store from one struct type to a different type with compatible layout and members. + #[arg(long, default_value = "false")] + pub relax_struct_store: bool, + + /// Allow allocating an object of a pointer type and returning a pointer value from a function + /// in logical addressing mode. + #[arg(long, default_value = "false")] + pub relax_logical_pointer: bool, + + /// Enable `VK_KHR_relaxed_block_layout` when checking standard uniform, + /// storage buffer, and push constant layouts. + /// This is the default when targeting Vulkan 1.1 or later. + #[arg(long, default_value = "false")] + pub relax_block_layout: bool, + + /// Enable `VK_KHR_uniform_buffer_standard_layout` when checking standard uniform buffer layouts. + #[arg(long, default_value = "false")] + pub uniform_buffer_standard_layout: bool, + + /// Enable `VK_EXT_scalar_block_layout` when checking standard uniform, storage buffer, and push + /// constant layouts. + /// Scalar layout rules are more permissive than relaxed block layout so in effect this will + /// override the --relax-block-layout option. + #[arg(long, default_value = "false")] + pub scalar_block_layout: bool, + + /// Skip checking standard uniform / storage buffer layout. Overrides any --relax-block-layout + /// or --scalar-block-layout option. + #[arg(long, default_value = "false")] + pub skip_block_layout: bool, + + /// Preserve unused descriptor bindings. Useful for reflection. + #[arg(long, default_value = "false")] + pub preserve_bindings: bool, +} + +impl BuildArgs { + /// Clap value parser for `SpirvMetadata`. + fn spirv_metadata(metadata: &str) -> Result { + match metadata { + "none" => Ok(SpirvMetadata::None), + "name-variables" => Ok(SpirvMetadata::NameVariables), + "full" => Ok(SpirvMetadata::Full), + _ => Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)), + } + } + + /// Clap value parser for `Capability`. + fn spirv_capability(capability: &str) -> Result { + spirv::Capability::from_str(capability).map_or_else( + |()| Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)), + Ok, + ) + } +} + +#[derive(clap::Parser, Debug, serde::Deserialize, serde::Serialize)] +pub struct InstallArgs { + #[clap(long, hide(true), default_value = "INTERNALLY_SET")] + pub dylib_path: std::path::PathBuf, + + /// Directory containing the shader crate to compile. + #[clap(long, default_value = "./")] + pub shader_crate: std::path::PathBuf, + + /// Source of `spirv-builder` dependency + /// Eg: "https://github.com/Rust-GPU/rust-gpu" + #[clap(long)] + pub spirv_builder_source: Option, + + /// Version of `spirv-builder` dependency. + /// * If `--spirv-builder-source` is not set, then this is assumed to be a crates.io semantic + /// version such as "0.9.0". + /// * If `--spirv-builder-source` is set, then this is assumed to be a Git "commitsh", such + /// as a Git commit hash or a Git tag, therefore anything that `git checkout` can resolve. + #[clap(long, verbatim_doc_comment)] + pub spirv_builder_version: Option, + + /// Rust toolchain channel to use to build `spirv-builder`. + /// + /// This must be compatible with the `spirv_builder` argument as defined in the `rust-gpu` repo. + #[clap(long)] + pub rust_toolchain: Option, + + /// Force `spirv-builder-cli` and `rustc_codegen_spirv` to be rebuilt. + #[clap(long)] + pub force_spirv_cli_rebuild: bool, + + /// Assume "yes" to "Install Rust toolchain: [y/n]" prompt. + #[clap(long, action)] + pub auto_install_rust_toolchain: bool, +} diff --git a/crates/spirv-builder-cli/src/lib.rs b/crates/spirv-builder-cli/src/lib.rs index b26c7b5..982f23d 100644 --- a/crates/spirv-builder-cli/src/lib.rs +++ b/crates/spirv-builder-cli/src/lib.rs @@ -1,4 +1,6 @@ -//! Wire types for `cargo-gpu` and `spirv-builder-cli`. +pub mod args; + +pub use spirv; /// Shader source and entry point that can be used to create shader linkage. #[derive(serde::Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -28,31 +30,6 @@ impl Linkage { } } -/// `spirv-builder-cli` command line interface. -#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Args { - /// Path to rustc_codegen_spirv dylib. - pub dylib_path: std::path::PathBuf, - - /// Directory containing the shader crate to compile. - pub shader_crate: std::path::PathBuf, - - /// Shader target. - pub shader_target: String, - - /// Path to target spec file. - pub path_to_target_spec: std::path::PathBuf, - - /// Set cargo default-features. - pub no_default_features: bool, - - /// Set cargo features. - pub features: Vec, - - /// Path to the output directory for the compiled shaders. - pub output_dir: std::path::PathBuf, -} - /// A built shader entry-point, used in `spirv-builder-cli` to generate /// a `build-manifest.json` used by `cargo-gpu`. #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] diff --git a/crates/spirv-builder-cli/src/main.rs b/crates/spirv-builder-cli/src/main.rs index 496c2cc..0390d0c 100644 --- a/crates/spirv-builder-cli/src/main.rs +++ b/crates/spirv-builder-cli/src/main.rs @@ -1,5 +1,9 @@ -//! This program builds rust-gpu shader crates and writes generated spv files -//! into the main source repo. +/// NB: For developing this file it will probably help to temporarily move the `"crates/spirv-builder-cli"` +/// line from the `exclude` to `members` section of the root `Cargo.toml` file. This will allow +/// `rust-analyzer` to run on the file. We can't permanently keep it there because each of the +/// `spirv-builder-*` features depends on a different Rust toolchain which `cargo check/clippy` +/// can't build all at once. +pub mod args; #[cfg(feature = "spirv-builder-pre-cli")] use spirv_builder_pre_cli as spirv_builder; @@ -8,8 +12,7 @@ use spirv_builder_pre_cli as spirv_builder; use spirv_builder_0_10 as spirv_builder; use spirv_builder::{CompileResult, MetadataPrintout, ModuleResult, SpirvBuilder}; - -use spirv_builder_cli::{Args, ShaderModule}; +use spirv_builder_cli::ShaderModule; const RUSTC_NIGHTLY_CHANNEL: &str = "${CHANNEL}"; @@ -39,7 +42,7 @@ fn set_codegen_spirv_location(dylib_path: std::path::PathBuf) { std::env::set_var(env_var, path); } -fn main() { +pub fn main() { env_logger::builder().init(); set_rustup_toolchain(); @@ -49,44 +52,59 @@ fn main() { "running spirv-builder-cli from '{}'", std::env::current_dir().unwrap().display() ); - let args = serde_json::from_str(&args[1]).unwrap(); - log::debug!("compiling with args: {args:#?}"); - let Args { - dylib_path, - shader_crate, - shader_target, - path_to_target_spec, - no_default_features, - features, - output_dir, - } = args; + log::debug!("with args: {args:#?}"); + let args: args::AllArgs = serde_json::from_str(&args[1]).unwrap(); + + let spirv_metadata = match args.build.spirv_metadata { + args::SpirvMetadata::None => spirv_builder::SpirvMetadata::None, + args::SpirvMetadata::NameVariables => spirv_builder::SpirvMetadata::NameVariables, + args::SpirvMetadata::Full => spirv_builder::SpirvMetadata::Full, + }; let CompileResult { entry_points, module, } = { - let mut builder = SpirvBuilder::new(shader_crate, &shader_target) - .print_metadata(MetadataPrintout::None) - .multimodule(true); + let mut builder = SpirvBuilder::new(args.install.shader_crate, &args.build.target) + .deny_warnings(args.build.deny_warnings) + .release(!args.build.debug) + .multimodule(args.build.multimodule) + .spirv_metadata(spirv_metadata) + .relax_struct_store(args.build.relax_struct_store) + .relax_logical_pointer(args.build.relax_logical_pointer) + .relax_block_layout(args.build.relax_block_layout) + .uniform_buffer_standard_layout(args.build.uniform_buffer_standard_layout) + .scalar_block_layout(args.build.scalar_block_layout) + .skip_block_layout(args.build.skip_block_layout) + .preserve_bindings(args.build.preserve_bindings) + .print_metadata(spirv_builder::MetadataPrintout::None); + + for capability in &args.build.capability { + builder = builder.capability(*capability); + } + + for extension in &args.build.extension { + builder = builder.extension(extension); + } #[cfg(feature = "spirv-builder-pre-cli")] { - set_codegen_spirv_location(dylib_path); + set_codegen_spirv_location(args.install.dylib_path); } #[cfg(feature = "spirv-builder-0_10")] { builder = builder - .rustc_codegen_spirv_location(dylib_path) - .target_spec(path_to_target_spec); + .rustc_codegen_spirv_location(args.install.dylib_path) + .target_spec(args.build.shader_target); - if no_default_features { + if args.build.no_default_features { log::info!("setting cargo --no-default-features"); builder = builder.shader_crate_default_features(false); } - if !features.is_empty() { - log::info!("setting --features {features:?}"); - builder = builder.shader_crate_features(features); + if !args.build.features.is_empty() { + log::info!("setting --features {:?}", args.build.features); + builder = builder.shader_crate_features(args.build.features); } } @@ -95,7 +113,7 @@ fn main() { }; log::debug!("found entry points: {entry_points:#?}"); - let dir = output_dir; + let dir = args.build.output_dir; let mut shaders = vec![]; match module { ModuleResult::MultiModule(modules) => { diff --git a/justfile b/justfile index ff42105..977b442 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,6 @@ [group: 'ci'] build-shader-template: - cargo install --path crates/cargo-gpu - cargo gpu install --shader-crate crates/shader-crate-template --auto-install-rust-toolchain - cargo gpu build --shader-crate crates/shader-crate-template --output-dir test-shaders - ls -lah test-shaders - cat test-shaders/manifest.json + scripts/build_shader_template.sh [group: 'ci'] setup-lints: diff --git a/scripts/build_shader_template.sh b/scripts/build_shader_template.sh new file mode 100755 index 0000000..55a931a --- /dev/null +++ b/scripts/build_shader_template.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Unix and OSX have different versions of this argument. +SED_INPLACE="-i" +if [ "$(uname)" = "Darwin" ]; then + SED_INPLACE="-i ''" +fi + +# Matching windows paths when they're at root (/) causes problems with how we canoniclaize paths. +TMP_DIR=".$(mktemp --directory)" + +cargo install --path crates/cargo-gpu +cargo gpu install --shader-crate crates/shader-crate-template --auto-install-rust-toolchain + +# We change the output directory in the shader crate's `Cargo.toml` rather than just using the simpler +# `--output-dir` CLI arg, because we want to smoke test that setting config in `Cargo.toml` works. +sed "$SED_INPLACE" "s#^output-dir =.*#output-dir = \"$TMP_DIR\"#" crates/shader-crate-template/Cargo.toml + +cargo gpu build --shader-crate crates/shader-crate-template --force-spirv-cli-rebuild +ls -lah "$TMP_DIR" +cat "$TMP_DIR"/manifest.json