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    pub fn metadata(&self) -> MetadataCommand {
50        let mut meta = cargo_metadata::MetadataCommand::new();
51        match self {
52            CargoFluxCommand::Check(check_options) => {
53                check_options.forward_to_metadata(&mut meta);
54            }
55            CargoFluxCommand::Clean(clean_options) => {
56                clean_options.forward_to_metadata(&mut meta);
57            }
58        }
59        meta
60    }
61}
62
63#[derive(clap::Args)]
64pub struct CheckOpts {
65    /// Error format [possible values: human, short, json, json-diagnostic-short, json-diagnostic-rendered-ansi, json-render-diagnostics]
66    #[arg(long, value_name = "FMT")]
67    message_format: Option<String>,
68
69    #[command(flatten)]
70    workspace: Workspace,
71    #[command(flatten)]
72    features: Features,
73    #[command(flatten)]
74    manifest: ManifestOptions,
75}
76
77impl CheckOpts {
78    fn forward_args(&self, cmd: &mut Command) {
79        let CheckOpts { message_format, workspace, features, manifest } = self;
80        if let Some(message_format) = &message_format {
81            cmd.args(["--message-format", message_format]);
82        }
83        workspace.forward_args(cmd);
84        features.forward_args(cmd);
85        manifest.forward_args(cmd);
86    }
87
88    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
89        let CheckOpts { message_format: _, workspace: _, features, manifest } = self;
90        features.forward_to_metadata(meta);
91        manifest.forward_to_metadata(meta);
92    }
93}
94
95#[derive(clap::Args)]
96pub struct CleanOpts {
97    #[command(flatten, next_help_heading = "Package Selection")]
98    package: Package,
99    #[command(flatten)]
100    features: Features,
101    #[command(flatten)]
102    manifest: ManifestOptions,
103}
104
105impl CleanOpts {
106    fn forward_args(&self, cmd: &mut Command) {
107        let CleanOpts { package, features, manifest } = self;
108        package.forward_args(cmd);
109        features.forward_args(cmd);
110        manifest.forward_args(cmd);
111    }
112
113    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
114        let CleanOpts { package: _, features, manifest } = self;
115        features.forward_to_metadata(meta);
116        manifest.forward_to_metadata(meta);
117    }
118}
119
120#[derive(Debug, clap::Args)]
121#[command(about = None, long_about = None, next_help_heading = "Package Selection")]
122pub struct Workspace {
123    #[command(flatten)]
124    pub package: Package,
125
126    #[arg(long)]
127    /// Process all packages in the workspace
128    pub workspace: bool,
129
130    #[arg(long, value_name = "SPEC")]
131    /// Exclude packages from being processed
132    pub exclude: Vec<String>,
133}
134
135impl Workspace {
136    fn forward_args(&self, cmd: &mut Command) {
137        let Workspace { package, workspace, exclude } = self;
138        package.forward_args(cmd);
139        if *workspace {
140            cmd.arg("--workspace");
141        }
142        if !exclude.is_empty() {
143            cmd.args(exclude.iter().flat_map(|package| ["--exclude", package]));
144        }
145    }
146}
147
148#[derive(Debug, clap::Args)]
149#[command(about = None, long_about = None)]
150pub struct Package {
151    #[arg(short, long, value_name = "SPEC")]
152    /// Package to process (see `cargo help pkgid`)
153    pub package: Vec<String>,
154}
155
156impl Package {
157    fn forward_args(&self, cmd: &mut Command) {
158        let Package { package } = self;
159        if !package.is_empty() {
160            cmd.args(package.iter().flat_map(|package| ["--package", package]));
161        }
162    }
163}
164
165#[derive(Default, Clone, Debug, PartialEq, Eq, clap::Args)]
166#[command(about = None, long_about = None, next_help_heading = "Feature Selection")]
167pub struct Features {
168    #[arg(short = 'F', long, value_delimiter = ' ')]
169    /// Space-separated list of features to activate
170    pub features: Vec<String>,
171    #[arg(long)]
172    /// Activate all available features
173    pub all_features: bool,
174    #[arg(long)]
175    /// Do not activate the `default` feature
176    pub no_default_features: bool,
177}
178
179impl Features {
180    fn forward_args(&self, cmd: &mut Command) {
181        let Features { features, all_features, no_default_features } = self;
182        if !features.is_empty() {
183            cmd.args(features.iter().flat_map(|feature| ["--features", feature]));
184        }
185        if *all_features {
186            cmd.arg("--all-features");
187        }
188        if *no_default_features {
189            cmd.arg("--no-default-features");
190        }
191    }
192
193    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
194        let Features { features, all_features, no_default_features } = self;
195        if *all_features {
196            meta.features(cargo_metadata::CargoOpt::AllFeatures);
197        }
198        if *no_default_features {
199            meta.features(cargo_metadata::CargoOpt::NoDefaultFeatures);
200        }
201        if !features.is_empty() {
202            meta.features(cargo_metadata::CargoOpt::SomeFeatures(features.clone()));
203        }
204    }
205}
206
207#[derive(Debug, clap::Args)]
208#[command(next_help_heading = "Manifest Options")]
209pub struct ManifestOptions {
210    #[arg(long, name = "PATH")]
211    /// Path to Cargo.toml
212    manifest_path: Option<Utf8PathBuf>,
213    /// Run without accessing the network
214    #[arg(long)]
215    offline: bool,
216}
217
218impl ManifestOptions {
219    fn forward_args(&self, cmd: &mut Command) {
220        let ManifestOptions { manifest_path, offline } = self;
221        if let Some(manifest_path) = &manifest_path {
222            cmd.args(["--manifest-path", manifest_path.as_str()]);
223        }
224        if *offline {
225            cmd.arg("--offline");
226        }
227    }
228
229    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
230        // TODO(nilehmann) should we pass offline to metadata?
231        let ManifestOptions { manifest_path, offline: _ } = self;
232        if let Some(manifest_path) = &manifest_path {
233            meta.manifest_path(manifest_path);
234        }
235    }
236}