diff --git a/CHANGELOG.md b/CHANGELOG.md index 6652853..ce81842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Changed +- [PR#80](https://github.com/EmbarkStudios/spdx/pull/80) changed how `Licensee::satisfies` works for GNU licenses again, it now requires that the license ids match exactly. This is incredibly pedantic but means it's up to consumers if the want to have a smarter comparison, I just don't want to have to care about GNU licenses, ever. + +### Fixed +- [PR#80](https://github.com/EmbarkStudios/spdx/pull/80) reverted a change introduced in [PR#78] that would auto-fixup GNU licenses to their non-deprecated forms eg. `GPL-2.0` => `GPL-2.0-only`. This is no longer done, resolving [#79](https://github.com/EmbarkStudios/spdx/issues/79). + ## [0.11.0] - 2025-08-08 ### Changed - [PR#78] removed `ParseMode::allow_lower_case_operators`, newer revisions of the SPDX spec allow all lower-case operators, making the option pointless. diff --git a/src/expression/parser.rs b/src/expression/parser.rs index 1f0ec84..e9b3402 100644 --- a/src/expression/parser.rs +++ b/src/expression/parser.rs @@ -196,26 +196,16 @@ impl Expression { match <.token { Token::Spdx(id) => match last_token { None | Some(Token::And | Token::Or | Token::OpenParen) => { - let id = if id.is_gnu() { - crate::gnu_license_id(id.name, false).ok_or_else(|| ParseError { + if !mode.allow_deprecated && id.is_deprecated() { + return Err(ParseError { original: original.to_owned(), - span: lt.span.clone(), - reason: Reason::UnknownLicense, - })? - } else { - if !mode.allow_deprecated && id.is_deprecated() { - return Err(ParseError { - original: original.to_owned(), - span: lt.span, - reason: Reason::DeprecatedLicenseId, - }); - } - - *id - }; + span: lt.span, + reason: Reason::DeprecatedLicenseId, + }); + } expr_queue.push(ExprNode::Req(ExpressionReq { - req: LicenseReq::from(id), + req: LicenseReq::from(*id), span: lt.span.start as u32..lt.span.end as u32, })); } diff --git a/src/licensee.rs b/src/licensee.rs index 7423ba3..41a719f 100644 --- a/src/licensee.rs +++ b/src/licensee.rs @@ -11,7 +11,7 @@ use std::fmt; /// /// ``` /// let licensee = spdx::Licensee::parse("GPL-2.0-or-later").unwrap(); -/// let req = spdx::LicenseReq::from(spdx::license_id("GPL-2.0-only").unwrap()); +/// let req = spdx::LicenseReq::from(spdx::license_id("GPL-2.0-or-later").unwrap()); /// /// assert!(licensee.satisfies(&req)); /// ``` @@ -89,7 +89,7 @@ impl Licensee { })??; match lt.token { - Token::Spdx(mut id) => { + Token::Spdx(id) => { if !mode.allow_deprecated && id.is_deprecated() { return Err(ParseError { original: original.to_owned(), @@ -98,15 +98,6 @@ impl Licensee { }); } - if id.is_gnu() && !id.name.ends_with("-or-later") && !id.name.ends_with("-only") - { - id = crate::gnu_license_id(id.name, false).ok_or_else(|| ParseError { - original: original.to_owned(), - span: lt.span, - reason: Reason::UnknownLicense, - })?; - } - LicenseItem::Spdx { id, or_later: false, @@ -211,37 +202,6 @@ impl Licensee { _ => return false, } } - } else if a.is_gnu() && b.is_gnu() { - let abn = a.base(); - let bbn = b.base(); - - if abn != bbn { - return false; - } - - // GFDL has the annoying -no -invariants...variants, both - // sides have to agree on all or none - if abn == "GFDL" - && a.name.contains("-invariants") ^ b.name.contains("-invariants") - || a.name.contains("-no-") ^ b.name.contains("-no-") - { - return false; - } - - let Some(av) = a.version() else { - return false; - }; - let Some(bv) = b.version() else { - return false; - }; - - if b.name.ends_with("-or-later") { - if av < bv { - return false; - } - } else if abn != bbn || av != bv { - return false; - } } else { return false; } diff --git a/tests/check.rs b/tests/check.rs index 970cceb..9fa6baf 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -84,7 +84,7 @@ fn complex() { false && (false || false) => |req| exact!(req, "Apache-2.0"), true && (false || false) => |req| exact!(req, "MIT"), true && (false || true) => |req| exact!(req, "MIT") || exact!(req, "BSD-3-Clause"), - true && (true || false) => |req| exact!(req, "MIT") || exact!(req, "LGPL-3.0-or-later"), + true && (true || false) => |req| exact!(req, "MIT") || exact!(req, "LGPL-2.1-or-later"), ]); } @@ -171,24 +171,13 @@ fn or_later() { fn lgpl_only() { check!("LGPL-2.1-only" => [ false => |req| exact!(req, "LGPL-2.0"), - true => |req| exact!(req, "LGPL-2.1"), + false => |req| exact!(req, "LGPL-2.1"), + true => |req| exact!(req, "LGPL-2.1-only"), false => |req| exact!(req, "LGPL-3.0"), //false => |req| exact!(req, "LGPL-4.0"), ]); } -#[test] -fn gpl_or_later() { - check!("GPL-3.0-or-later" => [ - false => |req| exact!(req, "GPL-1.0"), - false => |req| exact!(req, "GPL-2.0"), - true => |req| exact!(req, "GPL-3.0-only"), - true => |req| exact!(req, "GPL-3.0"), - true => |req| exact!(req, "GPL-3.0-or-later"), - //true => |req| exact!(req, "GPL-4.0"), - ]); -} - #[test] fn gpl_or_later_plus_strict() { spdx::Expression::parse("GPL-2.0+").unwrap_err(); @@ -201,15 +190,6 @@ fn gpl_or_later_plus_lax() { #[test] fn gpl_pedantic() { - // | Licensee | GPL-1.0-only | GPL-1.0-or-later | GPL-2.0-only | GPL-2.0-or-later | GPL-3.0-only | GPL-3.0-or-later | - // | ----------------- | -- | -- | -- | -- | -- | -- | - // | GPL-1.0-only | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | - // | GPL-1.0-or-later | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | - // | GPL-2.0-only | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | - // | GPL-2.0-or-later | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | - // | GPL-3.0-only | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | - // | GPL-3.0-or-later | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | - const ONE_ONLY: &str = "GPL-1.0-only"; const ONE_LATER: &str = "GPL-1.0-or-later"; const TWO_ONLY: &str = "GPL-2.0-only"; @@ -217,88 +197,88 @@ fn gpl_pedantic() { const THREE_ONLY: &str = "GPL-3.0-only"; const THREE_LATER: &str = "GPL-3.0-or-later"; + const COLUMNS: &[&str] = &[ + ONE_ONLY, + ONE_LATER, + TWO_ONLY, + TWO_LATER, + THREE_ONLY, + THREE_LATER, + ]; + let table = [ - ( - ONE_ONLY, - [ - (ONE_ONLY, true), - (ONE_LATER, true), - (TWO_ONLY, false), - (TWO_LATER, false), - (THREE_ONLY, false), - (THREE_LATER, false), - ], - ), - ( - ONE_LATER, - [ - (ONE_ONLY, true), - (ONE_LATER, true), - (TWO_ONLY, false), - (TWO_LATER, false), - (THREE_ONLY, false), - (THREE_LATER, false), - ], - ), - ( - TWO_ONLY, - [ - (ONE_ONLY, false), - (ONE_LATER, true), - (TWO_ONLY, true), - (TWO_LATER, true), - (THREE_ONLY, false), - (THREE_LATER, false), - ], - ), - ( - TWO_LATER, - [ - (ONE_ONLY, false), - (ONE_LATER, true), - (TWO_ONLY, true), - (TWO_LATER, true), - (THREE_ONLY, false), - (THREE_LATER, false), - ], - ), - ( - THREE_ONLY, - [ - (ONE_ONLY, false), - (ONE_LATER, true), - (TWO_ONLY, false), - (TWO_LATER, true), - (THREE_ONLY, true), - (THREE_LATER, true), - ], - ), - ( - THREE_LATER, - [ - (ONE_ONLY, false), - (ONE_LATER, true), - (TWO_ONLY, false), - (TWO_LATER, true), - (THREE_ONLY, true), - (THREE_LATER, true), - ], - ), + (ONE_ONLY, [true, false, false, false, false, false]), + (ONE_LATER, [false, true, false, false, false, false]), + (TWO_ONLY, [false, false, true, false, false, false]), + (TWO_LATER, [false, false, false, true, false, false]), + (THREE_ONLY, [false, false, false, false, true, false]), + (THREE_LATER, [false, false, false, false, false, true]), ]; + fn header(s: &mut String) { + s.push_str("| Licensee | GPL-1.0-only | GPL-1.0-or-later | GPL-2.0-only | GPL-2.0-or-later | GPL-3.0-only | GPL-3.0-or-later |\n"); + s.push_str("| ---------------- | ------------ | ---------------- | ------------ | ---------------- | ------------ | ---------------- |\n"); + } + + fn fill(s: &mut String, c: &str, len: usize) { + for _ in 0..len - c.len() { + s.push(' '); + } + } + + fn lic(s: &mut String, lic: &str) { + s.push_str("| "); + s.push_str(lic); + fill(s, lic, 16); + s.push_str(" | "); + } + for (licensee, items) in table { + let mut expected = String::new(); + let exp = &mut expected; + header(exp); + + let mut actual = String::new(); + let act = &mut actual; + header(act); + + lic(exp, licensee); + lic(act, licensee); + let lic = spdx::Licensee::parse(licensee).unwrap(); - for (req, passes) in items { + let mut fail = false; + for (col, passes) in COLUMNS.iter().zip(items.iter()) { let req = spdx::LicenseReq { license: spdx::LicenseItem::Spdx { - id: spdx::license_id(req).unwrap(), + id: spdx::license_id(col).unwrap(), or_later: false, }, exception: None, }; - assert_eq!(lic.satisfies(&req), passes); + exp.push_str(if *passes { "+" } else { "-" }); + fill(exp, " ", col.len()); + + exp.push_str(" | "); + + let satisfies = lic.satisfies(&req); + + act.push_str(if satisfies { "+" } else { "-" }); + fill(act, " ", col.len()); + act.push_str(" | "); + + fail |= *passes != satisfies; + } + + exp.push('\n'); + act.push('\n'); + + if fail { + panic!( + "{}", + similar_asserts::SimpleDiff::from_str(exp, act, "expected", "calculated") + ); } } } @@ -307,11 +287,11 @@ fn gpl_pedantic() { fn gfdl() { check!("GFDL-1.2-or-later" => [ false => |req| exact!(req, "GFDL-1.1"), - true => |req| exact!(req, "GFDL-1.2"), - true => |req| exact!(req, "GFDL-1.3"), + false => |req| exact!(req, "GFDL-1.2"), + false => |req| exact!(req, "GFDL-1.3"), false => |req| exact!(req, "GFDL-1.1-or-later"), true => |req| exact!(req, "GFDL-1.2-or-later"), - true => |req| exact!(req, "GFDL-1.3-or-later"), + false => |req| exact!(req, "GFDL-1.3-or-later"), ]); check!("GFDL-1.2-invariants-or-later" => [ @@ -320,16 +300,16 @@ fn gfdl() { false => |req| exact!(req, "GFDL-1.2"), true => |req| exact!(req, "GFDL-1.2-invariants-or-later"), false => |req| exact!(req, "GFDL-1.3"), - true => |req| exact!(req, "GFDL-1.3-invariants-only"), + false => |req| exact!(req, "GFDL-1.3-invariants-only"), ]); check_lax!("GFDL-1.1-invariants+" => [ false => |req| exact!(req, "GFDL-1.1"), true => |req| exact!(req, "GFDL-1.1-invariants-or-later"), false => |req| exact!(req, "GFDL-1.2"), - true => |req| exact!(req, "GFDL-1.2-invariants-or-later"), + false => |req| exact!(req, "GFDL-1.2-invariants-or-later"), false => |req| exact!(req, "GFDL-1.3"), - true => |req| exact!(req, "GFDL-1.3-invariants-or-later"), + false => |req| exact!(req, "GFDL-1.3-invariants-or-later"), ]); check!("GFDL-1.2-invariants" => [ @@ -337,6 +317,7 @@ fn gfdl() { false => |req| exact!(req, "GFDL-1.1-invariants"), false => |req| exact!(req, "GFDL-1.2"), true => |req| exact!(req, "GFDL-1.2-invariants"), + false => |req| exact!(req, "GFDL-1.2-invariants-only"), false => |req| exact!(req, "GFDL-1.3"), false => |req| exact!(req, "GFDL-1.3-invariants"), ]); @@ -345,7 +326,8 @@ fn gfdl() { false => |req| exact!(req, "GFDL-1.1"), false => |req| exact!(req, "GFDL-1.1-invariants"), false => |req| exact!(req, "GFDL-1.2"), - true => |req| exact!(req, "GFDL-1.2-invariants"), + false => |req| exact!(req, "GFDL-1.2-invariants"), + true => |req| exact!(req, "GFDL-1.2-invariants-only"), false => |req| exact!(req, "GFDL-1.3"), false => |req| exact!(req, "GFDL-1.3-invariants"), ]); @@ -355,7 +337,8 @@ fn gfdl() { false => |req| exact!(req, "GFDL-1.1-invariants"), false => |req| exact!(req, "GFDL-1.2"), false => |req| exact!(req, "GFDL-1.2-invariants"), - true => |req| exact!(req, "GFDL-1.3"), + false => |req| exact!(req, "GFDL-1.3"), + true => |req| exact!(req, "GFDL-1.3-only"), false => |req| exact!(req, "GFDL-1.3-invariants"), ]); }