Skip to content

Commit a10389a

Browse files
committed
Make Cargo targets inherit package licenses
Signed-off-by: Peter Bestler <peter.bestler@liebherr.com>
1 parent d5c35fd commit a10389a

17 files changed

Lines changed: 147 additions & 0 deletions

File tree

cargo-cyclonedx/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Fixed
11+
12+
- Cargo target subcomponents now inherit the license of their package, including licenses inherited from the workspace.
13+
814
## 0.5.9 - 2026-03-19
915

1016
### Added

cargo-cyclonedx/src/generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ impl SbomGenerator {
306306
&package.version.to_string(),
307307
Some(bom_ref),
308308
);
309+
subcomponent.licenses.clone_from(&top_component.licenses);
309310

310311
// PURL subpaths are computed relative to the directory with the `Cargo.toml`
311312
// *for this specific package*, not the workspace root.

cargo-cyclonedx/src/main.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,47 @@ fn get_metadata(
192192

193193
#[cfg(test)]
194194
mod tests {
195+
use crate::{cli, generate_sboms};
196+
use clap::Parser;
197+
use cyclonedx_bom::models::{component::Component, license::Licenses};
195198
use cyclonedx_bom::prelude::NormalizedString;
199+
use std::path::PathBuf;
200+
201+
fn generate_fixture_package(fixture: &str) -> Component {
202+
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
203+
.join("tests/fixtures")
204+
.join(fixture)
205+
.join("Cargo.toml");
206+
let path_arg = format!("--manifest-path={}", manifest.display());
207+
let args = cli::Args::parse_from(["cyclonedx", &path_arg]);
208+
209+
let mut sboms = generate_sboms(&args).unwrap();
210+
assert_eq!(sboms.len(), 1, "fixture {fixture} must generate one SBOM");
211+
sboms
212+
.pop()
213+
.unwrap()
214+
.bom
215+
.metadata
216+
.unwrap()
217+
.component
218+
.unwrap()
219+
}
220+
221+
fn assert_fixture_license_is_inherited(fixture: &str, expected: Licenses) {
222+
let package = generate_fixture_package(fixture);
223+
assert_eq!(package.licenses.as_ref(), Some(&expected));
224+
225+
let targets = &package.components.as_ref().unwrap().0;
226+
assert_eq!(targets.len(), 2);
227+
for target in targets {
228+
assert_eq!(
229+
target.licenses.as_ref(),
230+
Some(&expected),
231+
"target {:?} did not inherit its package license",
232+
target.name,
233+
);
234+
}
235+
}
196236

197237
#[test]
198238
fn parse_toml_only_normal() {
@@ -216,6 +256,53 @@ mod tests {
216256
.0
217257
.iter()
218258
.all(|f| f.scope == Some(Scope::Required)));
259+
260+
let targets = &sboms[0]
261+
.bom
262+
.metadata
263+
.as_ref()
264+
.unwrap()
265+
.component
266+
.as_ref()
267+
.unwrap()
268+
.components
269+
.as_ref()
270+
.unwrap()
271+
.0;
272+
assert!(targets.iter().all(|target| target.licenses.is_none()));
273+
}
274+
275+
#[test]
276+
fn workspace_license_is_inherited_by_all_cargo_targets() {
277+
use cyclonedx_bom::models::license::LicenseChoice;
278+
279+
assert_fixture_license_is_inherited(
280+
"workspace_license",
281+
Licenses(vec![LicenseChoice::expression("LicenseRef-Proprietary")]),
282+
);
283+
}
284+
285+
#[test]
286+
fn package_license_is_inherited_by_all_cargo_targets() {
287+
use cyclonedx_bom::models::license::LicenseChoice;
288+
289+
assert_fixture_license_is_inherited(
290+
"package_license",
291+
Licenses(vec![LicenseChoice::expression("MIT")]),
292+
);
293+
}
294+
295+
#[test]
296+
fn package_license_file_is_inherited_by_all_cargo_targets() {
297+
use cyclonedx_bom::models::attached_text::AttachedText;
298+
use cyclonedx_bom::models::license::{License, LicenseChoice};
299+
let mut expected_license = License::named_license("Unknown");
300+
expected_license.text = Some(AttachedText::new(None, "Test license text.\n"));
301+
302+
assert_fixture_license_is_inherited(
303+
"package_license_file",
304+
Licenses(vec![LicenseChoice::License(expected_license)]),
305+
);
219306
}
220307

221308
#[test]

cargo-cyclonedx/tests/fixtures/package_license/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "package-license"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
7+
[workspace]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub fn library_target() {}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}

cargo-cyclonedx/tests/fixtures/package_license_file/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "package-license-file"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license-file = "LICENSE"
6+
7+
[workspace]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Test license text.

0 commit comments

Comments
 (0)