diff --git a/Cargo.lock b/Cargo.lock index 0a9f0d3..538bf0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,7 +255,9 @@ dependencies = [ "dialoguer", "regex", "reqwest", + "semver", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4cd615a..f2b242d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,9 @@ dialoguer = { version = "0.11.0", default-features = false } # API interaction serde = { features = ["derive"], version = "1.0.210" } +serde_json = "1.0.128" reqwest = { features = ["blocking", "json"], version = "0.12.7" } regex = "1.10.6" + +# Understanding package versions +semver = { version = "1.0.23", features = ["serde"] } diff --git a/src/external_cli/cargo/metadata.rs b/src/external_cli/cargo/metadata.rs new file mode 100644 index 0000000..14547e5 --- /dev/null +++ b/src/external_cli/cargo/metadata.rs @@ -0,0 +1,146 @@ +use std::{ffi::OsStr, path::PathBuf, process::Command}; + +use semver::{Version, VersionReq}; +use serde::Deserialize; + +use super::program; + +/// Create a command to run `cargo metadata`. +pub(crate) fn command() -> Command { + let mut command = Command::new(program()); + // The format version needs to be fixed for compatibility and to avoid a warning log + command.args(["metadata", "--format-version", "1"]); + command +} + +/// Try to obtain the Cargo metadata of this pacakge. +pub(crate) fn metadata() -> anyhow::Result { + metadata_with_args::<[&str; 0], &str>([]) +} + +/// Try to obtain the Cargo metadata of this pacakge. +/// +/// To see which additional args are available, [consult the `cargo metadata` documentation](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html) +/// or use `cargo metadata --help`. +pub(crate) fn metadata_with_args(additional_args: I) -> anyhow::Result +where + I: IntoIterator, + S: AsRef, +{ + let output = command().args(additional_args).output()?; + let metadata = serde_json::from_slice(&output.stdout)?; + Ok(metadata) +} + +/// Metadata information about the current package. +/// +/// See the [`cargo metadata` specification](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html#json-format). +#[derive(Debug, Deserialize)] +pub struct Metadata { + /// List of all packages in the workspace. + /// + /// It also includes all feature-enabled dependencies unless `--no-deps` is used. + packages: Vec, + /// List of members of the workspace. + /// + /// Each entry is the Package ID for the package. + workspace_members: Option>, + /// List of default members of the workspace. + /// + /// Each entry is the Package ID for the package. + workspace_default_members: Option>, + /// The absolute path to the build directory where Cargo places its output. + target_directory: PathBuf, + /// The absolute path to the root of the workspace. + workspace_root: Option, +} + +#[derive(Debug, Deserialize)] +pub struct Package { + /// The name of the package. + name: String, + /// The version of the package. + version: Version, + /// The Package ID for referring to the package within the document and as the `--package` + /// argument to many commands. + id: String, + /// List of Cargo targets. + targets: Vec, + /// Absolute path to this package's manifest. + manifest_path: PathBuf, + /// Optional string that is the default binary picked by cargo run. + default_run: Option, +} + +#[derive(Debug, Deserialize)] +pub struct Dependency { + /// The name of the dependency. + name: String, + /// The version requirement for the dependency. + /// + /// Dependencies without a version requirement have a value of `*`. + #[serde(default)] + req: VersionReq, + /// The dependency kind. + /// + /// `"dev"`, `"build"`, or `null` for a normal dependency. + #[serde(default)] + kind: DependencyKind, + /// The file system path for a local path dependency. + /// + /// Not present if not a path dependency. + path: Option, +} + +#[derive(Debug, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum DependencyKind { + #[default] + Normal, + Dev, + Build, + #[serde(untagged)] + Unknown(String), +} + +#[derive(Debug, Deserialize)] +pub struct Target { + kind: Vec, + /// The name of the target. + /// + /// For lib targets, dashes will be replaced with underscores. + name: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum TargetKind { + Lib, + Rlib, + Dylib, + ProcMacro, + Bin, + Example, + Test, + Bench, + CustomBuild, + #[serde(untagged)] + Unknown(String), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_determine_metadata_of_this_package() { + let metadata = metadata(); + assert!(metadata.is_ok()); + let metadata = metadata.unwrap(); + + assert!(metadata + .packages + .iter() + .any(|package| package.name == "bevy_cli")); + } +} diff --git a/src/external_cli/cargo/mod.rs b/src/external_cli/cargo/mod.rs index 52ec43b..5dabce1 100644 --- a/src/external_cli/cargo/mod.rs +++ b/src/external_cli/cargo/mod.rs @@ -6,6 +6,7 @@ use clap::{ArgAction, Args}; use super::arg_builder::ArgBuilder; pub(crate) mod build; +pub(crate) mod metadata; fn program() -> OsString { env::var_os("BEVY_CLI_CARGO").unwrap_or("cargo".into())