diff --git a/Cargo.toml b/Cargo.toml index 62ca2ec..a5c45be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,4 @@ partial_pub_fields = { level = "allow", priority = 1 } pattern_type_mismatch = { level = "allow", priority = 1 } std_instead_of_alloc = { level = "allow", priority = 1 } arbitrary_source_item_ordering = { level = "allow", priority = 1 } +missing_inline_in_public_items = { level = "allow", priority = 1 } diff --git a/crates/cargo-gpu/src/build.rs b/crates/cargo-gpu/src/build.rs index 2a7f158..5baf487 100644 --- a/crates/cargo-gpu/src/build.rs +++ b/crates/cargo-gpu/src/build.rs @@ -59,6 +59,11 @@ impl Build { /// Entrypoint pub fn run(&mut self) -> anyhow::Result<()> { let installed_backend = self.install.run()?; + let mut metadata = crate::metadata::MetadataCache::default(); + + if let Some(package) = self.install.package.as_ref() { + self.install.shader_crate = metadata.resolve_package_to_shader_crate(package)?; + } let _lockfile_mismatch_handler = LockfileMismatchHandler::new( &self.install.shader_crate, diff --git a/crates/cargo-gpu/src/config.rs b/crates/cargo-gpu/src/config.rs index f1a67fa..8c8257b 100644 --- a/crates/cargo-gpu/src/config.rs +++ b/crates/cargo-gpu/src/config.rs @@ -2,6 +2,8 @@ use anyhow::Context as _; use clap::Parser as _; +use crate::metadata::MetadataCache; + /// Config pub struct Config; @@ -23,10 +25,11 @@ impl Config { /// `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, + shader_crate_path: &std::path::Path, mut env_args: Vec, + metadata: &mut MetadataCache, ) -> anyhow::Result { - let mut config = crate::metadata::Metadata::as_json(shader_crate_path)?; + let mut config = metadata.as_json(shader_crate_path)?; env_args.retain(|arg| !(arg == "build" || arg == "install")); let cli_args_json = Self::cli_args_to_json(env_args)?; @@ -100,6 +103,7 @@ mod test { "--debug".to_owned(), "--auto-install-rust-toolchain".to_owned(), ], + &mut MetadataCache::default(), ) .unwrap(); assert!(!args.build.spirv_builder.release); @@ -122,7 +126,12 @@ mod test { ) .unwrap(); - let args = Config::clap_command_with_cargo_config(&shader_crate_path, vec![]).unwrap(); + let args = Config::clap_command_with_cargo_config( + &shader_crate_path, + vec![], + &mut MetadataCache::default(), + ) + .unwrap(); assert!(!args.build.spirv_builder.release); assert!(args.install.auto_install_rust_toolchain); } @@ -146,7 +155,12 @@ mod 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(); + let args = Config::clap_command_with_cargo_config( + &shader_crate_path, + vec![], + &mut MetadataCache::default(), + ) + .unwrap(); if cfg!(target_os = "windows") { assert_eq!(args.build.output_dir, std::path::Path::new("C:/the/moon")); } else { @@ -166,6 +180,7 @@ mod test { "--output-dir".to_owned(), "/the/river".to_owned(), ], + &mut MetadataCache::default(), ) .unwrap(); assert_eq!(args.build.output_dir, std::path::Path::new("/the/river")); @@ -185,7 +200,12 @@ mod test { ) .unwrap(); - let args = Config::clap_command_with_cargo_config(&shader_crate_path, vec![]).unwrap(); + let args = Config::clap_command_with_cargo_config( + &shader_crate_path, + vec![], + &mut MetadataCache::default(), + ) + .unwrap(); assert_eq!( args.build.spirv_builder.capabilities, vec![ @@ -207,6 +227,7 @@ mod test { "--manifest-file".to_owned(), "mymanifest".to_owned(), ], + &mut MetadataCache::default(), ) .unwrap(); assert_eq!(args.build.manifest_file, "mymanifest".to_owned()); diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index 2aa7bde..2cf3620 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -64,9 +64,16 @@ impl InstalledBackend { #[derive(clap::Parser, Debug, Clone, serde::Deserialize, serde::Serialize)] #[non_exhaustive] pub struct Install { + /// Cargo target to compile. + /// + /// Conflicts with `--shader-crate`. + #[clap(short, long, conflicts_with("shader_crate"))] + pub package: Option, + /// Directory containing the shader crate to compile. - #[clap(long, alias("package"), short_alias('p'), default_value = "./")] - #[serde(alias = "package")] + /// + /// Conflicts with `--package` or `-p`. + #[clap(long, default_value = "./", conflicts_with("package"))] pub shader_crate: PathBuf, #[expect( @@ -132,6 +139,7 @@ impl Install { #[must_use] pub const fn from_shader_crate(shader_crate: PathBuf) -> Self { Self { + package: None, shader_crate, spirv_builder_source: None, spirv_builder_version: None, diff --git a/crates/cargo-gpu/src/lib.rs b/crates/cargo-gpu/src/lib.rs index 1be7abb..4c0cb33 100644 --- a/crates/cargo-gpu/src/lib.rs +++ b/crates/cargo-gpu/src/lib.rs @@ -70,6 +70,7 @@ mod target_specs; mod test; pub use install::*; +pub use metadata::MetadataCache; pub use spirv_builder; /// Central function to write to the user. @@ -122,12 +123,19 @@ impl Command { /// # Errors /// Any errors during execution, usually printed to the user #[inline] - pub fn run(&self, env_args: Vec) -> anyhow::Result<()> { + pub fn run( + &self, + env_args: Vec, + metadata_cache: &mut metadata::MetadataCache, + ) -> anyhow::Result<()> { match &self { Self::Install(install) => { let shader_crate_path = &install.shader_crate; - let command = - config::Config::clap_command_with_cargo_config(shader_crate_path, env_args)?; + let command = config::Config::clap_command_with_cargo_config( + shader_crate_path, + env_args, + metadata_cache, + )?; log::debug!( "installing with final merged arguments: {:#?}", command.install @@ -136,8 +144,11 @@ impl Command { } Self::Build(build) => { let shader_crate_path = &build.install.shader_crate; - let mut command = - config::Config::clap_command_with_cargo_config(shader_crate_path, env_args)?; + let mut command = config::Config::clap_command_with_cargo_config( + shader_crate_path, + env_args, + metadata_cache, + )?; log::debug!("building with final merged arguments: {command:#?}"); if command.build.watch { diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index f40e3c6..449aa70 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -37,5 +37,6 @@ fn run() -> anyhow::Result<()> { .collect::>(); log::trace!("CLI args: {env_args:#?}"); let cli = Cli::parse_from(&env_args); - cli.command.run(env_args) + let mut metadata_cache = cargo_gpu::MetadataCache::default(); + cli.command.run(env_args, &mut metadata_cache) } diff --git a/crates/cargo-gpu/src/metadata.rs b/crates/cargo-gpu/src/metadata.rs index 0b330fb..00aeea1 100644 --- a/crates/cargo-gpu/src/metadata.rs +++ b/crates/cargo-gpu/src/metadata.rs @@ -1,14 +1,73 @@ //! Get config from the shader crate's `Cargo.toml` `[*.metadata.rust-gpu.*]` +use std::collections::HashMap; + +use anyhow::Context as _; use cargo_metadata::MetadataCommand; use serde_json::Value; -/// `Metadata` refers to the `[metadata.*]` section of `Cargo.toml` that `cargo` formally +/// A cache of metadata from various `Cargo.toml` files. +/// +/// "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; +#[derive(Debug, Default)] +pub struct MetadataCache { + /// Cached result of `MetadataCommand::new().exec()`. + inner: HashMap, +} + +impl MetadataCache { + /// Return the cached cargo metadata for the Cargo.toml at the given path, + /// or find it, populate the cache with it and return it. + fn get_metadata( + &mut self, + maybe_path_to_manifest_dir: Option<&std::path::Path>, + ) -> anyhow::Result<&cargo_metadata::Metadata> { + let path = if let Some(path) = maybe_path_to_manifest_dir { + path.to_path_buf() + } else { + std::env::current_dir().context("cannot determine the current working directory")? + }; + + if !self.inner.contains_key(&path) { + let metadata = MetadataCommand::new().current_dir(&path).exec()?; + self.inner.insert(path.clone(), metadata); + } + + self.inner.get(&path).context("unreachable") + } + + /// Resolve a package name to a crate directory. + /// + /// ## Errors + /// * if fetching cargo metadata fails. + /// * if no packages are listed in the cargo metadata. + /// * if the manifest path has no parent. + pub fn resolve_package_to_shader_crate( + &mut self, + package: &str, + ) -> anyhow::Result { + log::debug!("resolving package '{package}' to shader crate"); + let metadata = self.get_metadata(None)?; + + let meta_package = metadata + .packages + .iter() + .find(|pkg| pkg.name.as_str() == package) + .context("Package not found in metadata")?; + let shader_crate_path: std::path::PathBuf = meta_package + .manifest_path + .parent() + .context("manifest is missing a parent directory")? + .to_path_buf() + .into(); + log::debug!( + " determined shader crate path to be '{}'", + shader_crate_path.display() + ); + Ok(shader_crate_path) + } -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. @@ -16,8 +75,12 @@ impl Metadata { /// 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)?; + /// + /// ## Errors + /// Errors if cargo metadata cannot be found or if it cannot be operated on. + pub fn as_json(&mut self, path: &std::path::Path) -> anyhow::Result { + log::debug!("reading package metadata from {}", path.display()); + let cargo_json = self.get_cargo_toml_as_json(path)?; let config = Self::merge_configs(&cargo_json, path)?; Ok(config) } @@ -27,6 +90,7 @@ impl Metadata { cargo_json: &cargo_metadata::Metadata, path: &std::path::Path, ) -> anyhow::Result { + log::debug!("merging cargo metadata from {}", path.display()); let mut metadata = crate::config::Config::defaults_as_json()?; crate::config::Config::json_merge( &mut metadata, @@ -65,9 +129,10 @@ impl Metadata { /// Convert a `Cargo.toml` to JSON fn get_cargo_toml_as_json( - path: &std::path::PathBuf, + &mut self, + path: &std::path::Path, ) -> anyhow::Result { - Ok(MetadataCommand::new().current_dir(path).exec()?) + self.get_metadata(Some(path)).cloned() } /// Get any `rust-gpu` metadata set in the crate's `Cargo.toml` @@ -136,7 +201,7 @@ mod test { .exec() .unwrap(); metadata.packages.first_mut().unwrap().metadata = serde_json::json!({}); - let configs = Metadata::merge_configs(&metadata, Path::new("./")).unwrap(); + let configs = MetadataCache::merge_configs(&metadata, Path::new("./")).unwrap(); assert_eq!(configs["build"]["release"], Value::Bool(true)); assert_eq!( configs["install"]["auto_install_rust_toolchain"], @@ -160,7 +225,7 @@ mod test { } } }); - let configs = Metadata::merge_configs(&metadata, Path::new("./")).unwrap(); + let configs = MetadataCache::merge_configs(&metadata, Path::new("./")).unwrap(); assert_eq!(configs["build"]["release"], Value::Bool(false)); assert_eq!( configs["install"]["auto_install_rust_toolchain"], @@ -189,7 +254,7 @@ mod test { } } }); - let configs = Metadata::merge_configs(&metadata, Path::new(".")).unwrap(); + let configs = MetadataCache::merge_configs(&metadata, Path::new(".")).unwrap(); assert_eq!(configs["build"]["release"], Value::Bool(false)); assert_eq!( configs["install"]["auto_install_rust_toolchain"],