diff options
Diffstat (limited to 'mingling_cli/src/metadata')
| -rw-r--r-- | mingling_cli/src/metadata/cmd_metadata.rs | 17 | ||||
| -rw-r--r-- | mingling_cli/src/metadata/setup.rs | 136 |
2 files changed, 153 insertions, 0 deletions
diff --git a/mingling_cli/src/metadata/cmd_metadata.rs b/mingling_cli/src/metadata/cmd_metadata.rs new file mode 100644 index 0000000..18241a2 --- /dev/null +++ b/mingling_cli/src/metadata/cmd_metadata.rs @@ -0,0 +1,17 @@ +use cargo_metadata::Metadata; +use mingling::{ + LazyRes, + macros::{chain, dispatcher, pack}, +}; + +use crate::metadata::setup::ResMetadata; + +dispatcher!("metadata"); + +pack!(ResultMetadata = ResMetadata); + +#[chain] +pub fn handle_metadata(_: EntryMetadata, metadata: &mut LazyRes<ResMetadata>) -> Metadata { + let metadata = metadata.get_ref().clone(); + metadata.data().clone() +} diff --git a/mingling_cli/src/metadata/setup.rs b/mingling_cli/src/metadata/setup.rs new file mode 100644 index 0000000..198baf1 --- /dev/null +++ b/mingling_cli/src/metadata/setup.rs @@ -0,0 +1,136 @@ +use std::{env::current_dir, path::PathBuf}; + +use cargo_metadata::{CargoOpt, MetadataCommand}; +use mingling::{ + LazyInit, Program, + macros::program_setup, + picker::{IntoPicker, value::Flag}, + prelude::*, + res::ResCurrentDir, +}; + +/// Name of Cargo manifest file +pub const CARGO_TOML: &str = "Cargo.toml"; + +use cargo_metadata::Metadata; +use serde::Serialize; + +/// Resource holding parsed `cargo metadata` output. +/// +/// This is lazily initialized during program setup by calling `cargo metadata` +/// with the appropriate CLI flags (e.g., `--features`, `--all-features`, +/// `--no-default-features`, `--no-deps`, `--message-format`). +/// +/// Access the inner [`Metadata`] via [`ResMetadata::data()`], which panics if +/// called before initialization (guaranteed not to happen in normal usage). +#[derive(Default, Clone, Serialize)] +pub struct ResMetadata { + data: Option<Metadata>, +} + +/// Resource indicating whether the output format is JSON. +/// +/// Set to `true` when `--message-format json` (or similar) is passed. +/// Used by renderers to decide whether to serialize structs as JSON. +#[derive(Default, Clone, Serialize)] +pub struct ResUsingJson { + pub using: bool, +} + +impl ResMetadata { + /// Returns a reference to the parsed `cargo metadata`. + /// + /// # Panics + /// + /// This function does **not** panic in practice, because `ResMetadata` is + /// always initialized via [`LazyInit`] inside the program setup, and the + /// initialization either succeeds (setting `data` to `Some(...)`) or fails + /// by propagating the error from `cmd.exec().unwrap()`. Therefore, by the + /// time this getter is called, `self.data` is guaranteed to be `Some`. + pub fn data(&self) -> &Metadata { + self.data.as_ref().unwrap() + } +} + +#[program_setup] +pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { + let args = program.get_args().to_vec(); + + let ( + feature_args, + manifest_path, + message_format, + enable_all_features, + no_default_features, + no_deps, + ) = args + .pick(&arg![features: Vec<String>]) + .pick(&arg![manifest_path: Option<String>]) + .pick_or(&arg![message_format: String], || "disable".to_string()) + .pick(&arg![all_features: Flag]) + .pick(&arg![no_default_features: Flag]) + .pick(&arg![no_deps: Flag]) + .unwrap(); + + // Is Using Json + program.with_resource(ResUsingJson { + using: message_format.contains("json"), + }); + + // Current Dir + let current_dir = current_dir().unwrap(); + + // Leak `feature_args` into a static slice so it can be captured by the metadata closure. + // Since the process does not loop, this memory leak is harmless. + let feature_args_leaked: &[String] = Box::leak(feature_args.into_boxed_slice()); + let manifest_path_clone = manifest_path.clone(); + let current_dir_clone = current_dir.clone(); + + program.with_resource(ResMetadata::lazy_init(move || { + // Paths + let metadata_path = match manifest_path_clone { + Some(ref path_str) => PathBuf::from(path_str), + None => find_manifest().unwrap(), + }; + + // Cargo Metadata - bind to a longer-lived variable + let mut cmd = MetadataCommand::new(); + cmd.manifest_path(metadata_path) + .current_dir(¤t_dir_clone); + + if *enable_all_features { + cmd.features(CargoOpt::AllFeatures); + } + + if *no_default_features { + cmd.features(CargoOpt::NoDefaultFeatures); + } + + if *no_deps { + cmd.no_deps(); + } + + cmd.features(CargoOpt::SomeFeatures(feature_args_leaked.to_vec())); + + ResMetadata { + data: Some(cmd.exec().unwrap()), + } + })); + + // Current Dir + program.with_resource(ResCurrentDir::from(current_dir)); +} + +/// Find `Cargo.toml` by searching upward from `current_dir`. +fn find_manifest() -> Option<PathBuf> { + let mut dir = current_dir().ok()?; + loop { + let candidate = dir.join(CARGO_TOML); + if candidate.is_file() { + return Some(candidate); + } + if !dir.pop() { + return None; + } + } +} |
