flux_bin/
cargo_flux_opts.rs

1use std::{path::Path, process::Command};
2
3use cargo_metadata::{MetadataCommand, camino::Utf8PathBuf};
4
5use crate::{cargo_style, utils::get_version};
6
7#[derive(clap::Parser)]
8#[command(name = "cargo")]
9#[command(bin_name = "cargo")]
10#[command(styles = cargo_style::CLAP_STYLING)]
11pub enum Cli {
12    /// Flux's integration with Cargo
13    Flux {
14        #[command(flatten)]
15        check_opts: CheckOpts,
16
17        #[command(subcommand)]
18        command: Option<CargoFluxCommand>,
19    },
20}
21
22#[derive(clap::Subcommand)]
23#[command(version = get_version())]
24pub enum CargoFluxCommand {
25    /// Check a local package and its dependencies for errors using Flux.
26    /// This is the default command when no subcommand is provided.
27    Check(CheckOpts),
28    /// Remove artifacts that cargo-flux has generated in the past
29    Clean(CleanOpts),
30}
31
32impl CargoFluxCommand {
33    /// Returns a vector of arguments to forward to the cargo command
34    pub fn forward_args(&self, cmd: &mut Command, config_file: &Path) {
35        match self {
36            CargoFluxCommand::Check(check_opts) => {
37                cmd.arg("check");
38                check_opts.forward_args(cmd);
39            }
40            CargoFluxCommand::Clean(clean_opts) => {
41                cmd.arg("clean");
42                clean_opts.forward_args(cmd);
43            }
44        }
45        cmd.args(["--profile", "flux"]);
46        cmd.args(["--config".as_ref(), config_file.as_os_str()]);
47    }
48
49    /// Returns the cargo subcommand
50    pub fn cargo_subcommand(&self) -> &'static str {
51        match self {
52            CargoFluxCommand::Clean(_) => "clean",
53            CargoFluxCommand::Check(_) => "check",
54        }
55    }
56
57    pub fn metadata(&self) -> MetadataCommand {
58        let mut meta = cargo_metadata::MetadataCommand::new();
59        match self {
60            CargoFluxCommand::Check(check_options) => {
61                check_options.forward_to_metadata(&mut meta);
62            }
63            CargoFluxCommand::Clean(clean_options) => {
64                clean_options.forward_to_metadata(&mut meta);
65            }
66        }
67        meta
68    }
69}
70
71#[derive(clap::Args)]
72pub struct CheckOpts {
73    /// Error format [possible values: human, short, json, json-diagnostic-short, json-diagnostic-rendered-ansi, json-render-diagnostics]
74    #[arg(long, value_name = "FMT")]
75    message_format: Option<String>,
76
77    #[command(flatten)]
78    workspace: Workspace,
79    #[command(flatten)]
80    features: Features,
81    #[command(flatten)]
82    manifest: ManifestOptions,
83}
84
85impl CheckOpts {
86    fn forward_args(&self, cmd: &mut Command) {
87        let CheckOpts { message_format, workspace, features, manifest } = self;
88        if let Some(message_format) = &message_format {
89            cmd.args(["--message-format", message_format]);
90        }
91        workspace.forward_args(cmd);
92        features.forward_args(cmd);
93        manifest.forward_args(cmd);
94    }
95
96    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
97        let CheckOpts { message_format: _, workspace: _, features, manifest } = self;
98        features.forward_to_metadata(meta);
99        manifest.forward_to_metadata(meta);
100    }
101}
102
103#[derive(clap::Args)]
104pub struct CleanOpts {
105    #[command(flatten, next_help_heading = "Package Selection")]
106    package: Package,
107    #[command(flatten)]
108    features: Features,
109    #[command(flatten)]
110    manifest: ManifestOptions,
111}
112
113impl CleanOpts {
114    fn forward_args(&self, cmd: &mut Command) {
115        let CleanOpts { package, features, manifest } = self;
116        package.forward_args(cmd);
117        features.forward_args(cmd);
118        manifest.forward_args(cmd);
119    }
120
121    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
122        let CleanOpts { package: _, features, manifest } = self;
123        features.forward_to_metadata(meta);
124        manifest.forward_to_metadata(meta);
125    }
126}
127
128#[derive(Debug, clap::Args)]
129#[command(about = None, long_about = None, next_help_heading = "Package Selection")]
130pub struct Workspace {
131    #[command(flatten)]
132    pub package: Package,
133
134    #[arg(long)]
135    /// Process all packages in the workspace
136    pub workspace: bool,
137
138    #[arg(long, value_name = "SPEC")]
139    /// Exclude packages from being processed
140    pub exclude: Vec<String>,
141}
142
143impl Workspace {
144    fn forward_args(&self, cmd: &mut Command) {
145        let Workspace { package, workspace, exclude } = self;
146        package.forward_args(cmd);
147        if *workspace {
148            cmd.arg("--workspace");
149        }
150        if !exclude.is_empty() {
151            cmd.args(exclude.iter().flat_map(|package| ["--exclude", package]));
152        }
153    }
154}
155
156#[derive(Debug, clap::Args)]
157#[command(about = None, long_about = None)]
158pub struct Package {
159    #[arg(short, long, value_name = "SPEC")]
160    /// Package to process (see `cargo help pkgid`)
161    pub package: Vec<String>,
162}
163
164impl Package {
165    fn forward_args(&self, cmd: &mut Command) {
166        let Package { package } = self;
167        if !package.is_empty() {
168            cmd.args(package.iter().flat_map(|package| ["--package", package]));
169        }
170    }
171}
172
173#[derive(Default, Clone, Debug, PartialEq, Eq, clap::Args)]
174#[command(about = None, long_about = None, next_help_heading = "Feature Selection")]
175pub struct Features {
176    #[arg(short = 'F', long, value_delimiter = ' ')]
177    /// Space-separated list of features to activate
178    pub features: Vec<String>,
179    #[arg(long)]
180    /// Activate all available features
181    pub all_features: bool,
182    #[arg(long)]
183    /// Do not activate the `default` feature
184    pub no_default_features: bool,
185}
186
187impl Features {
188    fn forward_args(&self, cmd: &mut Command) {
189        let Features { features, all_features, no_default_features } = self;
190        if !features.is_empty() {
191            cmd.args(features.iter().flat_map(|feature| ["--features", feature]));
192        }
193        if *all_features {
194            cmd.arg("--all-features");
195        }
196        if *no_default_features {
197            cmd.arg("--no-default-features");
198        }
199    }
200
201    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
202        let Features { features, all_features, no_default_features } = self;
203        if *all_features {
204            meta.features(cargo_metadata::CargoOpt::AllFeatures);
205        }
206        if *no_default_features {
207            meta.features(cargo_metadata::CargoOpt::NoDefaultFeatures);
208        }
209        if !features.is_empty() {
210            meta.features(cargo_metadata::CargoOpt::SomeFeatures(features.clone()));
211        }
212    }
213}
214
215#[derive(Debug, clap::Args)]
216#[command(next_help_heading = "Manifest Options")]
217pub struct ManifestOptions {
218    #[arg(long, name = "PATH")]
219    /// Path to Cargo.toml
220    manifest_path: Option<Utf8PathBuf>,
221    /// Run without accessing the network
222    #[arg(long)]
223    offline: bool,
224}
225
226impl ManifestOptions {
227    fn forward_args(&self, cmd: &mut Command) {
228        let ManifestOptions { manifest_path, offline } = self;
229        if let Some(manifest_path) = &manifest_path {
230            cmd.args(["--manifest-path", manifest_path.as_str()]);
231        }
232        if *offline {
233            cmd.arg("--offline");
234        }
235    }
236
237    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
238        // TODO(nilehmann) should we pass offline to metadata?
239        let ManifestOptions { manifest_path, offline: _ } = self;
240        if let Some(manifest_path) = &manifest_path {
241            meta.manifest_path(manifest_path);
242        }
243    }
244}