From bb8e1c54e9b912154d7dc2fbb17f0e2c82f90f27 Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Sun, 9 Nov 2025 22:17:25 +0100 Subject: [PATCH 1/3] lib.licenses: add compound licenses --- ci/OWNERS | 2 +- lib/default.nix | 2 +- lib/licenses/default.nix | 7 ++ lib/licenses/helpers.nix | 152 +++++++++++++++++++++++++++++ lib/{ => licenses}/licenses.nix | 1 + lib/licenses/operators.nix | 110 +++++++++++++++++++++ pkgs/stdenv/generic/check-meta.nix | 6 +- 7 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 lib/licenses/default.nix create mode 100644 lib/licenses/helpers.nix rename lib/{ => licenses}/licenses.nix (99%) create mode 100644 lib/licenses/operators.nix diff --git a/ci/OWNERS b/ci/OWNERS index b8fc4ecf920c..1b4b3e68ef9b 100644 --- a/ci/OWNERS +++ b/ci/OWNERS @@ -268,7 +268,7 @@ pkgs/development/python-modules/buildcatrust/ @ajs124 @lukegb @mweinelt /pkgs/applications/editors/jetbrains @leona-ya @theCapypara # Licenses -/lib/licenses.nix @alyssais @emilazy @jopejoe1 +/lib/licenses @alyssais @emilazy @jopejoe1 # Qt /pkgs/development/libraries/qt-5 @K900 @NickCao @SuperSandro2000 @ttuegel diff --git a/lib/default.nix b/lib/default.nix index 15949d1cdf36..751738a98965 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -73,7 +73,7 @@ let types = callLibs ./types.nix; # constants - licenses = callLibs ./licenses.nix; + licenses = callLibs ./licenses; sourceTypes = callLibs ./source-types.nix; systems = callLibs ./systems; diff --git a/lib/licenses/default.nix b/lib/licenses/default.nix new file mode 100644 index 000000000000..7e260e98833f --- /dev/null +++ b/lib/licenses/default.nix @@ -0,0 +1,7 @@ +{ lib }: +let + licenses = import ./licenses.nix { inherit lib; }; + operators = import ./operators.nix; + helpers = import ./helpers.nix { inherit lib; }; +in +licenses // operators // helpers diff --git a/lib/licenses/helpers.nix b/lib/licenses/helpers.nix new file mode 100644 index 000000000000..b234515284f8 --- /dev/null +++ b/lib/licenses/helpers.nix @@ -0,0 +1,152 @@ +{ lib }: +rec { + /** + Evaluate a license expression for a given predicate. + + # Example + + ```nix + evaluateProperty (x: x.free) true (with lib.licenses; AND [ ncsa (WITH asl20 llvm-exception) ]) + ``` + # Type + + ``` + evaluateProperty :: Function -> Bool -> AttrSet -> Bool + ``` + + # Arguments + + - [predicate] checks for each license included in the license expression + - [permissive] whether to apply checks permissive or reciprocal + - [license] license expression to check + */ + evaluateProperty = + predicate: permissive: license: + let + OR = if permissive then lib.any else lib.all; + AND = if permissive then lib.all else lib.any; + in + if license.licenseType == "simple" then + predicate license + else if license.licenseType == "compound" then + if license.operator == "OR" then + OR (x: evaluateProperty predicate permissive x) license.licenses + else if license.operator == "AND" then + AND (x: evaluateProperty predicate permissive x) license.licenses + else + throw "Unknown license operator" + else if license.licenseType == "exception" then + AND (x: evaluateProperty predicate permissive x) [ + license.license + license.exception + ] + else if license.licenseType == "plus" then + evaluateProperty predicate permissive license.license + else + throw "Unknown license type or legacy license"; + + /** + Check whether a license expression is free. + + # Example + + ```nix + isFree (with lib.licenses; (AND [ ncsa (WITH asl20 llvm-exception) ])) + => true + ``` + + # Type + + ``` + isFree :: AttrSet -> Bool + ``` + + # Arguments + + - [license] License expression to check if free + */ + isFree = evaluateProperty (x: x.free) true; + + /** + Check whether a license expression is redistributable. + + # Example + + ```nix + isRedistributable (with lib.licenses; (AND [ ncsa (WITH asl20 llvm-exception) ])) + => true + ``` + + # Type + + ``` + isRedistributable :: AttrSet -> Bool + ``` + + # Arguments + + - [license] License expression to check if redistributable + */ + isRedistributable = evaluateProperty (x: x.redistributable) true; + + /** + Check whether any of the given licenses is required in the license expression. + + # Example + + ```nix + containsLicenses [ lib.licenses.asl20 ] (with lib.licenses; (AND [ ncsa (WITH asl20 llvm-exception) ])) + => true + ``` + + # Type + + ``` + containsLicenses :: List -> AttrSet -> Bool + ``` + + # Arguments + + - [licenses] List of licenses to look + - [license] License expression to check + */ + containsLicenses = licenses: evaluateProperty (x: lib.lists.elem x licenses) false; + + /** + Convert a license expression to an SPDX license expression string. + + # Example + + ```nix + toSPDX (with lib.licenses; AND [ ncsa (WITH asl20 llvm-exception) ]) + => "NCSA AND (Apache-2.0 WITH LLVM-exception)" + ``` + + # Type + + ``` + toSPDX :: AttrSet -> String + ``` + + # Arguments + + - [license] License expression which to convert to spdx expression + */ + toSPDX = + license: + let + mkBracket = + x: + if x.licenseType == "compound" || x.licenseType == "exception" then "(${toSPDX x})" else toSPDX x; + in + if license.licenseType == "simple" then + license.spdxId or "LicenseRef-nixos-${license.shortName}" + else if license.licenseType == "compound" then + lib.concatMapStringsSep " ${license.operator} " (x: mkBracket x) license.licenses + else if license.licenseType == "exception" then + "${mkBracket license.license} ${license.operator} ${mkBracket license.exception}" + else if license.licenseType == "plus" then + "${mkBracket license.license}${license.operator}" + else + throw "Unknown license type"; +} diff --git a/lib/licenses.nix b/lib/licenses/licenses.nix similarity index 99% rename from lib/licenses.nix rename to lib/licenses/licenses.nix index 416891232e84..56f29b690495 100644 --- a/lib/licenses.nix +++ b/lib/licenses/licenses.nix @@ -21,6 +21,7 @@ let deprecated redistributable ; + licenseType = "simple"; } // optionalAttrs (attrs ? spdxId) { inherit spdxId; diff --git a/lib/licenses/operators.nix b/lib/licenses/operators.nix new file mode 100644 index 000000000000..c7dab4a1469a --- /dev/null +++ b/lib/licenses/operators.nix @@ -0,0 +1,110 @@ +{ + /** + This should be used when there is a choice of which license expression to use. + This is a disjunctive binary "OR" operator. + + # Example + + ```nix + OR [ lib.licenses.mit lib.licenses.asl20 ] + => { licenseType = "compound"; operator = "OR"; licenses = [ lib.licenses.mit lib.licenses.asl20 ] }; + ``` + + # Type + + ``` + OR :: List -> AttrSet + ``` + + # Arguments + + - [licenses] Possible licenses to choose from + */ + OR = licenses: { + licenseType = "compound"; + operator = "OR"; + inherit licenses; + }; + + /** + Create a compound licenses where the user needs to follow both licenses, + eqivialent of spdx `and` modifier. + + # Example + + ```nix + AND [ lib.licenses.mit lib.licenses.asl20 ] + => { licenseType = "compound"; operator = "AND"; licenses = [ lib.licenses.mit lib.licenses.asl20 ] }; + ``` + + # Type + + ``` + AND :: List -> AttrSet + ``` + + # Arguments + + - [licenses] Licenses required to use + */ + AND = licenses: { + licenseType = "compound"; + operator = "AND"; + inherit licenses; + }; + + /** + Create a licenses exception where a license has a license exception, + eqivialent of spdx `with` modifier. + + # Example + + ```nix + WITH lib.licenses.lgpl21Only lib.licenses.ocamlLgplLinkingException + => { licenseType = "exception"; operator = "WITH"; license = lib.licenses.lgpl21Only; exception = lib.licenses.ocamlLgplLinkingException; }; + ``` + + # Type + + ``` + WITH :: AttrSet -> AttrSet -> AttrSet + ``` + + # Arguments + + - [license] License to which the exception applies + - [exception] Exception to apply + */ + WITH = license: exception: { + licenseType = "compound"; + operator = "WITH"; + inherit license exception; + }; + + /** + Create a licenses which can be upgraded to any later version of itself, + eqivialent of spdx `+` modifier + + # Example + + ```nix + PLUS lib.licenses.lgpl21Only + => { licenseType = "plus"; operator = "+"; license = lib.licenses.lgpl21Only; }; + ``` + + # Type + + ``` + PLUS :: AttrSet -> AttrSet + ``` + + # Arguments + + - [license] License to wich apply an exception + */ + PLUS = license: { + licenseType = "plus"; + operator = "+"; + inherit license; + }; +} diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index fbf72005f92d..6c3a49c4957c 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -90,6 +90,8 @@ let && ( if isList attrs.meta.license then any (l: elem l list) attrs.meta.license + else if attrs.meta.license ? "type" then + lib.licenses.containsLicenses list attrs.meta.license else elem attrs.meta.license list ); @@ -103,7 +105,9 @@ let isUnfree = licenses: - if isAttrs licenses then + if isAttrs licenses && licenses ? "type" then + !(lib.licenses.isFree licenses) + else if isAttrs licenses then !(licenses.free or true) # TODO: Returning false in the case of a string is a bug that should be fixed. # In a previous implementation of this function the function body From dbdb37908f5393fb4dc5ebcae799f86f72b0f27c Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Thu, 22 Jan 2026 20:36:13 +0100 Subject: [PATCH 2/3] lib/tests: add tests for lib.licenses --- lib/licenses/operators.nix | 6 +-- lib/tests/misc.nix | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/lib/licenses/operators.nix b/lib/licenses/operators.nix index c7dab4a1469a..db7cc25d37c6 100644 --- a/lib/licenses/operators.nix +++ b/lib/licenses/operators.nix @@ -76,7 +76,7 @@ - [exception] Exception to apply */ WITH = license: exception: { - licenseType = "compound"; + licenseType = "exception"; operator = "WITH"; inherit license exception; }; @@ -88,8 +88,8 @@ # Example ```nix - PLUS lib.licenses.lgpl21Only - => { licenseType = "plus"; operator = "+"; license = lib.licenses.lgpl21Only; }; + PLUS lib.licenses.eupl11 + => { licenseType = "plus"; operator = "+"; license = lib.licenses.eupl11; }; ``` # Type diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index da0979756720..916994152754 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -5012,4 +5012,103 @@ runTests { testReplaceElemAtOutOfRange = testingThrow (lib.replaceElemAt [ 1 2 3 ] 5 "a"); testReplaceElemAtNegative = testingThrow (lib.replaceElemAt [ 1 2 3 ] (-1) "a"); + + testIsFree = { + expr = lib.licenses.isFree ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ + lib.licenses.free + lib.licenses.unfree + ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = true; + }; + + testIsUnfree = { + expr = lib.licenses.isFree ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ lib.licenses.unfree ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = false; + }; + + testIsRedistributable = { + expr = lib.licenses.isRedistributable ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ + lib.licenses.free + lib.licenses.unfree + ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = true; + }; + + testIsUnredistributable = { + expr = lib.licenses.isRedistributable ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ lib.licenses.unfree ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = false; + }; + + testContainsLicenses = { + expr = lib.licenses.containsLicenses [ lib.licenses.mit ] ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ + lib.licenses.free + lib.licenses.unfree + ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = true; + }; + + testToSPDX = { + expr = lib.licenses.toSPDX ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ + lib.licenses.free + lib.licenses.unfree + ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = "MIT AND (LicenseRef-nixos-free OR LicenseRef-nixos-unfree) AND (Apache-2.0 WITH LLVM-exception) AND EUPL-1.1+"; + }; + + testEvaluateProperty = { + expr = lib.licenses.evaluateProperty (x: x.deprecated) true ( + lib.licenses.AND [ + (lib.licenses.mit) + (lib.licenses.OR [ + lib.licenses.free + lib.licenses.unfree + ]) + (lib.licenses.WITH lib.licenses.asl20 lib.licenses.llvm-exception) + (lib.licenses.PLUS lib.licenses.eupl11) + ] + ); + expected = false; + }; } From baf97485d9bfd2255839b5b7d67ca2ce69979383 Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Sun, 9 Nov 2025 22:44:56 +0100 Subject: [PATCH 3/3] llvm: make use of compound licenses --- .../compilers/llvm/common/common-let.nix | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkgs/development/compilers/llvm/common/common-let.nix b/pkgs/development/compilers/llvm/common/common-let.nix index 84fd098ff3fa..d8b636feac5d 100644 --- a/pkgs/development/compilers/llvm/common/common-let.nix +++ b/pkgs/development/compilers/llvm/common/common-let.nix @@ -12,19 +12,19 @@ rec { llvm_meta = { license = with lib.licenses; - [ ncsa ] - ++ - # Contributions after June 1st, 2024 are only licensed under asl20 and - # llvm-exception: https://github.com/llvm/llvm-project/pull/92394 - lib.optionals (lib.versionAtLeast release_version "19") [ - asl20 - llvm-exception - ]; + # Contributions after June 1st, 2024 are only licensed under asl20 and + # llvm-exception: https://github.com/llvm/llvm-project/pull/92394 + if lib.versionAtLeast release_version "19" then + AND [ + ncsa + (WITH asl20 llvm-exception) + ] + else + ncsa; teams = [ lib.teams.llvm lib.teams.security-review ]; - # See llvm/cmake/config-ix.cmake. platforms = lib.platforms.aarch64