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