guppy_cmdlib/
lib.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Support for CLI operations with guppy, with structopt integration.
5//!
6//! This library allows translating command-line arguments into guppy's data structures.
7
8#[cfg(feature = "proptest1")]
9pub mod proptest;
10
11use clap::{ArgEnum, Parser};
12use color_eyre::eyre::Result;
13use guppy::{
14    graph::{
15        cargo::{CargoResolverVersion, InitialsPlatform},
16        feature::{named_feature_filter, FeatureSet, StandardFeatures},
17        PackageGraph,
18    },
19    platform::{Platform, PlatformSpec, TargetFeatures},
20    MetadataCommand,
21};
22use std::{env, path::PathBuf};
23
24/// Support for packages and features.
25///
26/// The options here mirror Cargo's.
27#[derive(Debug, Parser)]
28pub struct PackagesAndFeatures {
29    #[clap(long = "package", short = 'p')]
30    /// Packages to start the query from (default: entire workspace)
31    pub packages: Vec<String>,
32
33    #[clap(long = "features-only")]
34    /// Packages that take part in feature unification but aren't in the result set (default: none)
35    pub features_only: Vec<String>,
36
37    // TODO: support --workspace and --exclude
38    /// List of features to activate across all packages
39    #[clap(long = "features", use_value_delimiter = true)]
40    pub features: Vec<String>,
41
42    /// Activate all available features
43    #[clap(long = "all-features")]
44    pub all_features: bool,
45
46    /// Do not activate the `default` feature
47    #[clap(long = "no-default-features")]
48    pub no_default_features: bool,
49}
50
51impl PackagesAndFeatures {
52    /// Evaluates this struct against the given graph, and converts it into the initials and
53    /// features-only `FeatureSet`s.
54    pub fn make_feature_sets<'g>(
55        &self,
56        graph: &'g PackageGraph,
57    ) -> Result<(FeatureSet<'g>, FeatureSet<'g>)> {
58        let package_set = if self.packages.is_empty() {
59            graph.resolve_workspace()
60        } else {
61            graph.resolve_workspace_names(self.packages.iter())?
62        };
63        let features_only_set = if self.features_only.is_empty() {
64            graph.resolve_none()
65        } else {
66            graph.resolve_workspace_names(self.features_only.iter())?
67        };
68
69        let base_filter = match (self.all_features, self.no_default_features) {
70            (true, _) => StandardFeatures::All,
71            (false, false) => StandardFeatures::Default,
72            (false, true) => StandardFeatures::None,
73        };
74        // TODO: support package/feature format
75        // TODO: support feature name validation similar to cargo
76        let mut feature_filter =
77            named_feature_filter(base_filter, self.features.iter().map(|s| s.as_str()));
78
79        Ok((
80            package_set.to_feature_set(&mut feature_filter),
81            features_only_set.to_feature_set(&mut feature_filter),
82        ))
83    }
84}
85
86// Identical to guppy's CargoResolverVersion, except with additional string metadata generated
87// for matching.
88#[derive(ArgEnum, Clone, Copy, Debug)]
89pub enum CargoResolverVersionCmd {
90    V1,
91    V1Install,
92    V2,
93    V3,
94}
95
96#[derive(ArgEnum, Clone, Copy, Debug)]
97pub enum InitialsPlatformCmd {
98    Host,
99    Standard,
100    ProcMacrosOnTarget,
101}
102
103/// Support for options like the Cargo resolver version.
104#[derive(Clone, Debug, Parser)]
105pub struct CargoResolverOpts {
106    #[clap(long = "include-dev")]
107    /// Include dev-dependencies of initial packages (default: false)
108    pub include_dev: bool,
109
110    #[clap(long = "initials-platform")]
111    #[clap(arg_enum, default_value_t = InitialsPlatformCmd::Standard)]
112    /// Include initial proc-macros on target platform (default: false)
113    pub initials_platform: InitialsPlatformCmd,
114
115    #[clap(long = "resolver-version")]
116    #[clap(arg_enum, default_value_t = CargoResolverVersionCmd::V1)]
117    /// Cargo resolver version to use
118    pub resolver_version: CargoResolverVersionCmd,
119}
120
121impl CargoResolverVersionCmd {
122    /// Converts to guppy's CargoResolverVersion.
123    pub fn to_guppy(self) -> CargoResolverVersion {
124        match self {
125            CargoResolverVersionCmd::V1 => CargoResolverVersion::V1,
126            CargoResolverVersionCmd::V1Install => CargoResolverVersion::V1Install,
127            CargoResolverVersionCmd::V2 => CargoResolverVersion::V2,
128            CargoResolverVersionCmd::V3 => CargoResolverVersion::V3,
129        }
130    }
131}
132
133impl InitialsPlatformCmd {
134    /// Converts to guppy's InitialsPlatform.
135    pub fn to_guppy(self) -> InitialsPlatform {
136        match self {
137            InitialsPlatformCmd::Host => InitialsPlatform::Host,
138            InitialsPlatformCmd::Standard => InitialsPlatform::Standard,
139            InitialsPlatformCmd::ProcMacrosOnTarget => InitialsPlatform::ProcMacrosOnTarget,
140        }
141    }
142}
143
144/// Context for invoking the `cargo metadata` command.
145///
146/// The options mirror Cargo's.
147#[derive(Clone, Debug, Parser)]
148pub struct CargoMetadataOptions {
149    /// Path to Cargo.toml
150    #[clap(long)]
151    pub manifest_path: Option<PathBuf>,
152}
153
154impl CargoMetadataOptions {
155    /// Returns the current directory.
156    pub fn current_dir(&self) -> Result<PathBuf> {
157        Ok(env::current_dir()?)
158    }
159
160    /// Returns the absolute canonical manifest path.
161    pub fn abs_manifest_path(&self) -> Result<PathBuf> {
162        let cwd = self.current_dir()?;
163        let path = match &self.manifest_path {
164            Some(path) => cwd.join(path),
165            None => cwd.join("Cargo.toml"),
166        };
167        Ok(path.canonicalize()?)
168    }
169
170    /// Evaluates this struct and creates a `MetadataCommand`.
171    pub fn make_command(&self) -> MetadataCommand {
172        let mut command = MetadataCommand::new();
173        if let Some(manifest_path) = &self.manifest_path {
174            command.manifest_path(manifest_path);
175        }
176        command
177    }
178}
179
180/// Parse a given triple, the string "current", or "any", into a platform.
181///
182/// TODO: This should eventually support JSON specs as well, probably.
183pub fn string_to_platform_spec(s: Option<&str>) -> Result<PlatformSpec> {
184    match s {
185        Some("current") => Ok(PlatformSpec::build_target()?),
186        Some("always") => Ok(PlatformSpec::Always),
187        Some("any") => Ok(PlatformSpec::Any),
188        Some(triple) => Ok(Platform::new(triple.to_owned(), TargetFeatures::Unknown)?.into()),
189        None => Ok(PlatformSpec::Any),
190    }
191}