Skip to content

Commit 9f366f6

Browse files
authored
Merge pull request #634 from Shnatsel/cli-revamp
Revamp CLI, add CycloneDX spec version configuration
2 parents fb63759 + d396947 commit 9f366f6

File tree

6 files changed

+157
-187
lines changed

6 files changed

+157
-187
lines changed

cargo-cyclonedx/README.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ This produces a `bom.xml` file adjacent to every `Cargo.toml` file that exists i
3737
-f, --format <FORMAT>
3838
Output BOM format: json, xml
3939
40+
--describe <DESCRIBE>
41+
Possible values:
42+
- crate: Describe the entire crate in a single SBOM file, with Cargo targets as subcomponents. (default)
43+
- binaries: A separate SBOM is emitted for each binary (bin, cdylib) while all other targets are ignored
44+
- all-cargo-targets: A separate SBOM is emitted for each Cargo target, including things that aren't directly executable (e.g rlib)
45+
4046
-v, --verbose...
41-
Use verbose output (-vv very verbose/build.rs output)
47+
Use verbose output (-vv for debug logging, -vvv for tracing)
4248
43-
-q, --quiet
44-
No output printed to stdout
49+
-q, --quiet...
50+
Disable progress reports (-qq to suppress warnings)
4551
4652
--all-features
4753
Activate all available features
@@ -58,29 +64,26 @@ This produces a `bom.xml` file adjacent to every `Cargo.toml` file that exists i
5864
Defaults to the host target, as printed by 'rustc -vV'
5965
6066
--target-in-filename
61-
Include the target platform of the BOM in the filename. Implies --output-cdx
67+
Include the target platform of the BOM in the filename
6268
6369
-a, --all
6470
List all dependencies instead of only top-level ones (default)
6571
6672
--top-level
6773
List only top-level dependencies
6874
69-
--output-cdx
70-
Prepend file extension with .cdx
71-
72-
--output-pattern <PATTERN>
73-
Prefix patterns to use for the filename: bom, package
74-
75-
--output-prefix <FILENAME_PREFIX>
76-
Custom prefix string to use for the filename
75+
--override-filename <FILENAME>
76+
Custom string to use for the output filename
7777
7878
--license-strict
7979
Reject the deprecated '/' separator for licenses, treating 'MIT/Apache-2.0' as an error
8080
8181
--license-accept-named <LICENSE_ACCEPT_NAMED>
8282
Add license names which will not be warned about when parsing them as a SPDX expression fails
8383
84+
--spec-version <SPEC_VERSION>
85+
The CycloneDX specification version to output: `1.3` or `1.4`. Defaults to 1.3
86+
8487
-h, --help
8588
Print help (see a summary with '-h')
8689

cargo-cyclonedx/src/cli.rs

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use cargo_cyclonedx::{
22
config::{
3-
CdxExtension, CustomPrefix, Features, IncludedDependencies, LicenseParserOptions,
4-
OutputOptions, ParseMode, Pattern, PlatformSuffix, Prefix, PrefixError, SbomConfig, Target,
3+
Describe, Features, FilenameOverride, FilenameOverrideError, FilenamePattern,
4+
IncludedDependencies, LicenseParserOptions, OutputOptions, ParseMode, PlatformSuffix,
5+
SbomConfig, Target,
56
},
67
format::Format,
78
platform::host_platform,
89
};
910
use clap::{ArgAction, ArgGroup, Parser};
11+
use cyclonedx_bom::models::bom::SpecVersion;
1012
use std::collections::HashSet;
1113
use std::iter::FromIterator;
1214
use std::path;
@@ -23,23 +25,26 @@ pub enum Opts {
2325
#[derive(Parser, Debug)]
2426
#[clap(version)]
2527
#[clap(group(ArgGroup::new("dependencies-group").required(false).args(&["all", "top-level"])))]
26-
#[clap(group(ArgGroup::new("prefix-or-pattern-group").required(false).args(&["output-prefix", "output-pattern"])))]
2728
pub struct Args {
2829
/// Path to Cargo.toml
29-
#[clap(long = "manifest-path", value_name = "PATH")]
30+
#[clap(long = "manifest-path", value_name = "PATH", value_hint = clap::ValueHint::FilePath)]
3031
pub manifest_path: Option<path::PathBuf>,
3132

3233
/// Output BOM format: json, xml
3334
#[clap(long = "format", short = 'f', value_name = "FORMAT")]
3435
pub format: Option<Format>,
3536

36-
/// Use verbose output (-vv very verbose/build.rs output)
37+
// the ValueEnum derive provides ample help text
38+
#[clap(long = "describe")]
39+
pub describe: Option<Describe>,
40+
41+
/// Use verbose output (-vv for debug logging, -vvv for tracing)
3742
#[clap(long = "verbose", short = 'v', action = clap::ArgAction::Count)]
3843
pub verbose: u8,
3944

40-
/// No output printed to stdout
41-
#[clap(long = "quiet", short = 'q')]
42-
pub quiet: bool,
45+
/// Disable progress reports (-qq to suppress warnings)
46+
#[clap(long = "quiet", short = 'q', action = clap::ArgAction::Count)]
47+
pub quiet: u8,
4348

4449
// `--all-features`, `--no-default-features` and `--features`
4550
// are not mutually exclusive in Cargo, so we keep the same behavior here too.
@@ -55,7 +60,7 @@ pub struct Args {
5560
#[clap(long = "features", short = 'F')]
5661
pub features: Vec<String>,
5762

58-
/// The target to generate the SBOM for, or 'all' for all targets.
63+
/// The target platform to generate the SBOM for, or 'all' for all targets.
5964
#[clap(
6065
long = "target",
6166
long_help = "The target to generate the SBOM for, e.g. 'x86_64-unknown-linux-gnu'.
@@ -64,7 +69,7 @@ Defaults to the host target, as printed by 'rustc -vV'"
6469
)]
6570
pub target: Option<String>,
6671

67-
/// Include the target platform of the BOM in the filename. Implies --output-cdx
72+
/// Include the target platform of the BOM in the filename
6873
#[clap(long = "target-in-filename")]
6974
pub target_in_filename: bool,
7075

@@ -76,27 +81,13 @@ Defaults to the host target, as printed by 'rustc -vV'"
7681
#[clap(name = "top-level", long = "top-level", conflicts_with = "all")]
7782
pub top_level: bool,
7883

79-
/// Prepend file extension with .cdx
80-
#[clap(long = "output-cdx")]
81-
pub output_cdx: bool,
82-
83-
/// Prefix patterns to use for the filename: bom, package, binary, cargo-target
84-
/// Values other than 'bom' imply --output-cdx
85-
#[clap(
86-
name = "output-pattern",
87-
long = "output-pattern",
88-
value_name = "PATTERN"
89-
)]
90-
pub output_pattern: Option<Pattern>,
91-
92-
/// Custom prefix string to use for the filename
84+
/// Custom string to use for the output filename
9385
#[clap(
94-
name = "output-prefix",
95-
long = "output-prefix",
96-
value_name = "FILENAME_PREFIX",
97-
conflicts_with = "output-pattern"
86+
long = "override-filename",
87+
value_name = "FILENAME",
88+
conflicts_with = "describe"
9889
)]
99-
pub output_prefix: Option<String>,
90+
pub filename_override: Option<String>,
10091

10192
/// Reject the deprecated '/' separator for licenses, treating 'MIT/Apache-2.0' as an error
10293
#[clap(long = "license-strict")]
@@ -105,6 +96,10 @@ Defaults to the host target, as printed by 'rustc -vV'"
10596
/// Add license names which will not be warned about when parsing them as a SPDX expression fails
10697
#[clap(long = "license-accept-named", action=ArgAction::Append)]
10798
pub license_accept_named: Vec<String>,
99+
100+
/// The CycloneDX specification version to output: `1.3` or `1.4`. Defaults to 1.3
101+
#[clap(long = "spec-version")]
102+
pub spec_version: Option<SpecVersion>,
108103
}
109104

110105
impl Args {
@@ -115,15 +110,6 @@ impl Args {
115110
_ => None,
116111
};
117112

118-
let prefix = match (self.output_pattern, &self.output_prefix) {
119-
(Some(pattern), _) => Some(Prefix::Pattern(pattern)),
120-
(_, Some(prefix)) => {
121-
let prefix = CustomPrefix::new(prefix)?;
122-
Some(Prefix::Custom(prefix))
123-
}
124-
(_, _) => None,
125-
};
126-
127113
let features =
128114
if !self.all_features && !self.no_default_features && self.features.is_empty() {
129115
None
@@ -156,37 +142,23 @@ impl Args {
156142
Target::SingleTarget(target_string)
157143
});
158144

159-
let mut cdx_extension = match self.output_cdx {
160-
true => Some(CdxExtension::Included),
161-
false => None,
162-
};
163-
164145
let platform_suffix = match self.target_in_filename {
165146
true => PlatformSuffix::Included,
166147
false => PlatformSuffix::NotIncluded,
167148
};
168149

169-
// according to the CycloneDX spec, the file has either be called 'bom.xml'
170-
// or include the .cdx extension:
171-
// https://cyclonedx.org/specification/overview/#recognized-file-patterns
172-
if self.target_in_filename {
173-
cdx_extension = Some(CdxExtension::Included)
174-
}
175-
// Ditto for any kind of prefix or anything not named 'bom'
176-
if prefix.is_some() {
177-
cdx_extension = Some(CdxExtension::Included)
150+
let filename_pattern = match &self.filename_override {
151+
Some(string) => {
152+
let name_override = FilenameOverride::new(string)?;
153+
FilenamePattern::Custom(name_override)
154+
}
155+
None => FilenamePattern::CrateName,
178156
};
179157

180-
let output_options =
181-
if cdx_extension.is_none() && prefix.is_none() && !self.target_in_filename {
182-
None
183-
} else {
184-
Some(OutputOptions {
185-
cdx_extension: cdx_extension.unwrap_or_default(),
186-
prefix: prefix.unwrap_or_default(),
187-
platform_suffix,
188-
})
189-
};
158+
let output_options = Some(OutputOptions {
159+
filename: filename_pattern,
160+
platform_suffix,
161+
});
190162

191163
let license_parser = Some(LicenseParserOptions {
192164
mode: match self.license_strict {
@@ -196,21 +168,26 @@ impl Args {
196168
accept_named: HashSet::from_iter(self.license_accept_named.clone()),
197169
});
198170

171+
let describe = self.describe.clone();
172+
let spec_version = self.spec_version.clone();
173+
199174
Ok(SbomConfig {
200175
format: self.format,
201176
included_dependencies,
202177
output_options,
203178
features,
204179
target,
205180
license_parser,
181+
describe,
182+
spec_version,
206183
})
207184
}
208185
}
209186

210187
#[derive(Error, Debug, PartialEq, Eq)]
211188
pub enum ArgsError {
212-
#[error("Invalid prefix from CLI")]
213-
CustomPrefixError(#[from] PrefixError),
189+
#[error("Invalid filename")]
190+
FilenameOverrideError(#[from] FilenameOverrideError),
214191
}
215192

216193
#[cfg(test)]

0 commit comments

Comments
 (0)