lib.licenses: add compound licenses (#468378)

This commit is contained in:
lassulus
2026-04-19 13:09:21 +00:00
committed by GitHub
9 changed files with 385 additions and 12 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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;
+7
View File
@@ -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
+152
View File
@@ -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";
}
@@ -21,6 +21,7 @@ let
deprecated
redistributable
;
licenseType = "simple";
}
// optionalAttrs (attrs ? spdxId) {
inherit spdxId;
+110
View File
@@ -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 = "exception";
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.eupl11
=> { licenseType = "plus"; operator = "+"; license = lib.licenses.eupl11; };
```
# Type
```
PLUS :: AttrSet -> AttrSet
```
# Arguments
- [license] License to wich apply an exception
*/
PLUS = license: {
licenseType = "plus";
operator = "+";
inherit license;
};
}
+99
View File
@@ -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;
};
}
@@ -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
+5 -1
View File
@@ -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