flux_bin/
cargo_flux_opts.rs

1use std::{path::Path, process::Command};
2
3use cargo_metadata::{MetadataCommand, camino::Utf8PathBuf};
4
5use crate::cargo_style;
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        /// Print version information
21        #[arg(short = 'V', long, action = clap::ArgAction::SetTrue)]
22        version: bool,
23
24        /// Use verbose output (-Vv for more verbose output)
25        #[arg(short, long, action = clap::ArgAction::Count)]
26        verbose: u8,
27    },
28}
29
30#[derive(clap::Subcommand)]
31pub enum CargoFluxCommand {
32    /// Check a local package and its dependencies for errors using Flux.
33    /// This is the default command when no subcommand is provided.
34    Check(CheckOpts),
35    /// Remove artifacts that cargo-flux has generated in the past
36    Clean(CleanOpts),
37}
38
39impl CargoFluxCommand {
40    pub fn forward_args(&self, cmd: &mut Command, config_file: &Path) {
41        match self {
42            CargoFluxCommand::Check(check_opts) => {
43                cmd.arg("check");
44                check_opts.forward_args(cmd);
45            }
46            CargoFluxCommand::Clean(clean_opts) => {
47                cmd.arg("clean");
48                clean_opts.forward_args(cmd);
49            }
50        }
51        cmd.args(["--profile", "flux"]);
52        cmd.args(["--config".as_ref(), config_file.as_os_str()]);
53    }
54
55    pub fn metadata(&self) -> MetadataCommand {
56        let mut meta = cargo_metadata::MetadataCommand::new();
57        match self {
58            CargoFluxCommand::Check(check_options) => {
59                check_options.forward_to_metadata(&mut meta);
60            }
61            CargoFluxCommand::Clean(clean_options) => {
62                clean_options.forward_to_metadata(&mut meta);
63            }
64        }
65        meta
66    }
67}
68
69#[derive(clap::Args)]
70pub struct CheckOpts {
71    /// Error format [possible values: human, short, json, json-diagnostic-short, json-diagnostic-rendered-ansi, json-render-diagnostics]
72    #[arg(long, value_name = "FMT")]
73    message_format: Option<String>,
74
75    #[command(flatten)]
76    workspace: Workspace,
77    #[command(flatten)]
78    features: Features,
79    #[command(flatten)]
80    compilation: CompilationOptions,
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, compilation, 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        compilation.forward_args(cmd);
94        manifest.forward_args(cmd);
95    }
96
97    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
98        let CheckOpts { features, manifest, .. } = self;
99        features.forward_to_metadata(meta);
100        manifest.forward_to_metadata(meta);
101    }
102}
103
104#[derive(clap::Args)]
105pub struct CleanOpts {
106    #[command(flatten, next_help_heading = "Package Selection")]
107    package: Package,
108    #[command(flatten)]
109    features: Features,
110    #[command(flatten)]
111    manifest: ManifestOptions,
112}
113
114impl CleanOpts {
115    fn forward_args(&self, cmd: &mut Command) {
116        let CleanOpts { package, features, manifest } = self;
117        package.forward_args(cmd);
118        features.forward_args(cmd);
119        manifest.forward_args(cmd);
120    }
121
122    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
123        let CleanOpts { package: _, features, manifest } = self;
124        features.forward_to_metadata(meta);
125        manifest.forward_to_metadata(meta);
126    }
127}
128
129#[derive(Debug, clap::Args)]
130#[command(about = None, long_about = None, next_help_heading = "Package Selection")]
131pub struct Workspace {
132    #[command(flatten)]
133    pub package: Package,
134
135    #[arg(long)]
136    /// Process all packages in the workspace
137    pub workspace: bool,
138
139    #[arg(long, value_name = "SPEC")]
140    /// Exclude packages from being processed
141    pub exclude: Vec<String>,
142}
143
144impl Workspace {
145    fn forward_args(&self, cmd: &mut Command) {
146        let Workspace { package, workspace, exclude } = self;
147        package.forward_args(cmd);
148        if *workspace {
149            cmd.arg("--workspace");
150        }
151        if !exclude.is_empty() {
152            cmd.args(exclude.iter().flat_map(|package| ["--exclude", package]));
153        }
154    }
155}
156
157#[derive(Debug, clap::Args)]
158#[command(about = None, long_about = None)]
159pub struct Package {
160    #[arg(short, long, value_name = "SPEC")]
161    /// Package to process (see `cargo help pkgid`)
162    pub package: Vec<String>,
163}
164
165impl Package {
166    fn forward_args(&self, cmd: &mut Command) {
167        let Package { package } = self;
168        if !package.is_empty() {
169            cmd.args(package.iter().flat_map(|package| ["--package", package]));
170        }
171    }
172}
173
174#[derive(Default, Clone, Debug, PartialEq, Eq, clap::Args)]
175#[command(about = None, long_about = None, next_help_heading = "Feature Selection")]
176pub struct Features {
177    #[arg(short = 'F', long, value_delimiter = ' ')]
178    /// Space-separated list of features to activate
179    pub features: Vec<String>,
180    #[arg(long)]
181    /// Activate all available features
182    pub all_features: bool,
183    #[arg(long)]
184    /// Do not activate the `default` feature
185    pub no_default_features: bool,
186}
187
188impl Features {
189    fn forward_args(&self, cmd: &mut Command) {
190        let Features { features, all_features, no_default_features } = self;
191        if !features.is_empty() {
192            cmd.args(features.iter().flat_map(|feature| ["--features", feature]));
193        }
194        if *all_features {
195            cmd.arg("--all-features");
196        }
197        if *no_default_features {
198            cmd.arg("--no-default-features");
199        }
200    }
201
202    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
203        let Features { features, all_features, no_default_features } = self;
204        if *all_features {
205            meta.features(cargo_metadata::CargoOpt::AllFeatures);
206        }
207        if *no_default_features {
208            meta.features(cargo_metadata::CargoOpt::NoDefaultFeatures);
209        }
210        if !features.is_empty() {
211            meta.features(cargo_metadata::CargoOpt::SomeFeatures(features.clone()));
212        }
213    }
214}
215
216#[derive(Debug, clap::Args)]
217#[command(next_help_heading = "Compilation Options")]
218pub struct CompilationOptions {
219    #[arg(short = 'j', long, value_name = "N")]
220    /// Number of parallel jobs, defaults to # of CPUs.
221    pub jobs: Option<u32>,
222    #[arg(long)]
223    /// Do not abort the build as soon as there is an error
224    pub keep_going: bool,
225    #[arg(long, value_name = "TRIPLE")]
226    /// Check for the target triple
227    pub target: Vec<String>,
228}
229
230impl CompilationOptions {
231    fn forward_args(&self, cmd: &mut Command) {
232        let CompilationOptions { jobs, keep_going, target } = self;
233        if let Some(jobs) = jobs {
234            cmd.args(["--jobs", &format!("{jobs}")]);
235        }
236        if *keep_going {
237            cmd.arg("--keep-going");
238        }
239        for t in target {
240            cmd.args(["--target", t]);
241        }
242    }
243}
244
245#[derive(Debug, clap::Args)]
246#[command(next_help_heading = "Manifest Options")]
247pub struct ManifestOptions {
248    #[arg(long, name = "PATH")]
249    /// Path to Cargo.toml
250    manifest_path: Option<Utf8PathBuf>,
251    /// Run without accessing the network
252    #[arg(long)]
253    offline: bool,
254}
255
256impl ManifestOptions {
257    fn forward_args(&self, cmd: &mut Command) {
258        let ManifestOptions { manifest_path, offline } = self;
259        if let Some(manifest_path) = &manifest_path {
260            cmd.args(["--manifest-path", manifest_path.as_str()]);
261        }
262        if *offline {
263            cmd.arg("--offline");
264        }
265    }
266
267    fn forward_to_metadata(&self, meta: &mut MetadataCommand) {
268        // TODO(nilehmann) should we pass offline to metadata?
269        let ManifestOptions { manifest_path, offline: _ } = self;
270        if let Some(manifest_path) = &manifest_path {
271            meta.manifest_path(manifest_path);
272        }
273    }
274}