on iko/ghcjs-patch-master help sign in
Spdx license expressions (#859)

* Add parser combinators to parse SPDX license expressions The most important parser is `compoundExpression` it parses any SPDX license expression into a list of SPDX simple-expressions. The rationale is Nixpkgs' license metadata isn't capable of distinguishing between the AND and OR relationships. License exceptions aren't currently taken into account. * Add tests for the SPDX parser combinators I simply added a file with expressions that are expected to fail to parse or parse successfully. * Add the SPDX license list The SPDX license list as attrsets following the nixpkgs lib.licenses convention. This uses fetchurl which is not ideal because it's not available during pure evaluation. It does not yet include the SPDX exceptions list. * Refactor cabal-licenses.nix to use spdx/licenses.nix The handling of the generic licenses is undecided still. Some have been removed because they have better official identifiers. * Refactor license mapping in builders The common code in the comp- and setup-builders has been extracted and refactored to use the SPDX expression parser. * Use spdx-license-list-data from nixpkgs This conveniently solves the impurity problem with using fetchurl : ) I'm not sure threading `pkgs` through everything to get access to the spdx license list package is the right way to go about this. * hscolour to "LGPL-2.1-only" and remove "LGPL" * Use evalPackages for spdx and move shim to overlay * Better fix for LGPL packages. Co-authored-by: Hamish Mackenzie <Hamish.Mackenzie@iohk.io>

commit07df7005319079f84e595f1e9710ac6801966b6c authortoonn <toonn@toonn.io> dateThu 14 Jan 2021, 02:33 (6 years ago) parentd70a242
9 files changed+217−33
builder/comp-builder.nix+2−6 builder/setup-builder.nix+2−6 lib/cabal-licenses.nix+17−21 lib/default.nix+3 lib/spdx/cabal.nix+19 lib/spdx/licenses.nix+20 lib/spdx/parser.nix+115 lib/spdx/test.nix+32 modules/configuration-nix.nix+7
builder/comp-builder.nix+2−6
@@ -1,4 +1,4 @@
1−{ stdenv, buildPackages, ghc, lib, gobject-introspection ? null, haskellLib, makeConfigFiles, haddockBuilder, ghcForComponent, hsPkgs, compiler, runCommand, libffi, gmp, zlib, ncurses, numactl, nodejs }:
1+{ pkgs, stdenv, buildPackages, ghc, lib, gobject-introspection ? null, haskellLib, makeConfigFiles, haddockBuilder, ghcForComponent, hsPkgs, compiler, runCommand, libffi, gmp, zlib, ncurses, numactl, nodejs }:
22lib.makeOverridable (
33let self =
44{ componentId
@@ -295,11 +295,7 @@ let
295295 meta = {
296296 homepage = package.homepage or "";
297297 description = package.synopsis or "";
298− license =
299− let
300− license-map = import ../lib/cabal-licenses.nix lib;
301− in license-map.${package.license} or
302− (builtins.trace "WARNING: license \"${package.license}\" not found" license-map.LicenseRef-OtherLicense);
298+ license = haskellLib.cabalToNixpkgsLicense package.license;
303299 platforms = if platforms == null then stdenv.lib.platforms.all else platforms;
304300 };
305301
builder/setup-builder.nix+2−6
@@ -1,4 +1,4 @@
1−{ stdenv, lib, buildPackages, haskellLib, ghc, nonReinstallablePkgs, hsPkgs, makeSetupConfigFiles, pkgconfig }:
1+{ pkgs, stdenv, lib, buildPackages, haskellLib, ghc, nonReinstallablePkgs, hsPkgs, makeSetupConfigFiles, pkgconfig }:
22
33{ component, package, name, src, flags ? {}, revision ? null, patches ? [], defaultSetupSrc
44, preUnpack ? component.preUnpack, postUnpack ? component.postUnpack
@@ -55,11 +55,7 @@ let
5555 meta = {
5656 homepage = package.homepage or "";
5757 description = package.synopsis or "";
58− license =
59− let
60− license-map = import ../lib/cabal-licenses.nix lib;
61− in license-map.${package.license} or
62− (builtins.trace "WARNING: license \"${package.license}\" not found" license-map.LicenseRef-OtherLicense);
58+ license = haskellLib.cabalToNixpkgsLicense package.license;
6359 platforms = if component.platforms == null then stdenv.lib.platforms.all else component.platforms;
6460 };
6561
lib/cabal-licenses.nix+17−21
@@ -1,24 +1,20 @@
1−lib: with lib.licenses;
2−{ BSD-3-Clause = bsd3;
3− BSD-2-Clause = bsd2;
4− MIT = mit;
5− "MPL-2.0" = mpl20;
6− ISC = isc;
7− "LGPL-2.1-only" = lgpl21;
8− "LGPL-3.0-only" = lgpl3;
9− "GPL-2.0-only" = gpl2;
10− "GPL-2.0-or-later" = gpl2Plus;
11− "GPL-3.0-only" = gpl3;
12− "AGPL-3.0-only" = agpl3;
13− "AGPL-3.0-or-later" = agpl3Plus;
14− "Apache-2.0" = asl20;
15− "GPL-2.0-or-later AND BSD-3-Clause" = [gpl2Plus bsd3];
16− "NCSA" = ncsa;
1+pkgs:
2+let licenses = import spdx/licenses.nix pkgs;
3+in licenses // {
174 # Generic
18− LicenseRef-Apache = "Apache";
19− LicenseRef-GPL = "GPL";
20− LicenseRef-LGPL = "LGPL";
21− LicenseRef-PublicDomain = publicDomain;
22− LicenseRef-OtherLicense = null;
5+ LicenseRef-PublicDomain = {
6+ spdxId = "LicenseRef-PublicDomain";
7+ shortName = "Public Domain";
8+ fullName = "This work is dedicated to the Public Domain";
9+ url = "https://wikipedia.org/wiki/Public_domain";
10+ free = true;
11+ };
12+ LicenseRef-OtherLicense = {
13+ spdxId = "LicenseRef-OtherLicense";
14+ shortName = "Other License";
15+ fullName = "Unidentified Other License";
16+ url = "https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/";
17+ free = false;
18+ };
2319 NONE = null;
2420}
lib/default.nix+3
@@ -274,6 +274,9 @@ in {
274274 inherit pkgs;
275275 }) parseIndexState parseBlock;
276276
277+
278+ cabalToNixpkgsLicense = import ./spdx/cabal.nix pkgs;
279+
277280 # This function is like
278281 # `src + (if subDir == "" then "" else "/" + subDir)`
279282 # however when `includeSiblings` is set it maintains
lib/spdx/cabal.nixnew file+19
@@ -0,0 +1,19 @@
1+let
2+ spdx = import ./parser.nix;
3+in pkgs:
4+with builtins;
5+let
6+ # For better performance these are not in the
7+ # let block below (probably helps by increasing
8+ # the sharing)
9+ license-map = import ../cabal-licenses.nix pkgs;
10+ otherLicenseWarning = lic:
11+ trace "WARNING: license \"${lic}\" not found"
12+ license-map.LicenseRef-OtherLicense;
13+in license:
14+let
15+ licenses = spdx.compound-expression license;
16+in if licenses == []
17+ then otherLicenseWarning license
18+ else map (lic: license-map.${lic} or (otherLicenseWarning lic))
19+ (pkgs.lib.unique (head licenses)._1)
lib/spdx/licenses.nixnew file+20
@@ -0,0 +1,20 @@
1+pkgs:
2+with builtins; let
3+ licensesJSON = fromJSON (replaceStrings
4+ [ "\\u0026" "\\u0027" "\\u003d" ]
5+ [ "&" "'" "=" ]
6+ (readFile "${pkgs.evalPackages.spdx-license-list-data}/json/licenses.json")
7+ );
8+ dropFour = s: substring 0 (stringLength s - 4) s;
9+ toSpdx = lic: with lic;
10+ { spdxId = licenseId
11+ ; shortName = licenseId
12+ ; fullName = name
13+ ; url = dropFour detailsUrl + "html"
14+ ; free = isOsiApproved
15+ ;
16+ };
17+ toNamedValue = lic: { name = lic.spdxId; value = lic; };
18+in
19+
20+listToAttrs (map (l: toNamedValue (toSpdx l)) licensesJSON.licenses)
lib/spdx/parser.nixnew file+115
@@ -0,0 +1,115 @@
1+let String = { mempty = ""; mappend = a: b: a + b; };
2+ List = { singleton = x: [x]; };
3+ pair = a: b: { _1 = a; _2 = b; };
4+ Pair = { pure = s: pair String.mempty s;
5+ mappend = a: b: pair (String.mappend a._1 b._1) b._2;
6+ map = f: p: pair (f p._1) p._2;
7+ };
8+ compose = f: g: x: f (g x);
9+in with builtins; rec {
10+ # Parser a = String -> [(a, String)]
11+ string = str: s:
12+ let strL = stringLength str;
13+ sL = stringLength s;
14+ in if str == substring 0 strL s
15+ then [ (pair str (substring strL sL s)) ]
16+ else [];
17+
18+ regex = capture_groups: re: s:
19+ let result = match "(${re})(.*)" s;
20+ in if result == null
21+ then []
22+ else [ (pair (elemAt result 0) (elemAt result (1 + capture_groups))) ];
23+
24+ # Left-biased choice
25+ choice = cs: s:
26+ if cs == []
27+ then []
28+ else let c = head cs;
29+ cs' = tail cs;
30+ result = c s;
31+ in if result == []
32+ then choice cs' s
33+ else result;
34+
35+ optional = p: s:
36+ let result = p s;
37+ in if result == []
38+ then [ (Pair.pure s) ]
39+ else result;
40+
41+ chain = ps: s:
42+ if ps == []
43+ then [ (Pair.pure s) ]
44+ else let p = head ps;
45+ ps' = tail ps;
46+ result = p s;
47+ in if result == []
48+ then []
49+ else let r = head result;
50+ rest = chain ps' r._2;
51+ in if rest == []
52+ then []
53+ else let r' = head rest;
54+ in [ (Pair.mappend r r') ];
55+
56+ idstring = regex 0 "[-\.0-9a-zA-Z]+";
57+
58+ license-ref = let documentref = chain [ (string "DocumentRef-")
59+ idstring
60+ (string ":")
61+ ];
62+ in chain [
63+ (optional documentref)
64+ (string "LicenseRef-")
65+ idstring
66+ ];
67+
68+ simple-expression = choice [ license-ref
69+ (chain [ idstring (string "+") ])
70+ idstring
71+ ];
72+
73+ compound-expression = let wrap = compose (map (Pair.map List.singleton));
74+ in choice [
75+ (s: let result = simple-expression s;
76+ in if result == []
77+ then []
78+ else let r = head result;
79+ rest = chain [(string " WITH ") idstring ] r._2;
80+ in if rest == []
81+ then []
82+ else [(pair r._1 (head rest)._2)]
83+ )
84+ (s: let result = simple-expression s;
85+ in if result == []
86+ then []
87+ else let r = head result;
88+ firstLicense = r._1;
89+ operator = choice [ (string " AND ")
90+ (string " OR ")
91+ ]
92+ r._2;
93+ in if operator == []
94+ then []
95+ else let s' = (head operator)._2;
96+ licenses = compound-expression s';
97+ in if licenses == []
98+ then []
99+ else let ls = head licenses;
100+ in [(pair ([firstLicense] ++ ls._1) ls._2)]
101+ )
102+ (wrap simple-expression)
103+ (s: let openParen = string "(" s;
104+ in if openParen == []
105+ then []
106+ else let result = compound-expression (head openParen)._2;
107+ in if result == []
108+ then []
109+ else let r = head result;
110+ closeParen = string ")" r._2;
111+ in if closeParen == []
112+ then []
113+ else [(pair r._1 (head closeParen)._2)])
114+ ];
115+}
lib/spdx/test.nixnew file+32
@@ -0,0 +1,32 @@
1+let spdx = import ./parser.nix;
2+in {
3+ idstringF = [ (spdx.idstring "$@#!$") ];
4+ idstringP = [ (spdx.idstring "blah") ];
5+
6+ license-refF = [ (spdx.license-ref "LicenseRef-$@#!$") ];
7+ license-refP = [ (spdx.license-ref "LicenseRef-blah")
8+ (spdx.license-ref "DocumentRef-beep:LicenseRef-boop")
9+ ];
10+
11+ simple-expressionF = [ (spdx.simple-expression "$@#!$") ];
12+ simple-expressionP = [
13+ (spdx.simple-expression "blah")
14+ (spdx.simple-expression "blah+")
15+ (spdx.simple-expression "LicenseRef-blah")
16+ (spdx.simple-expression "DocumentRef-beep:LicenseRef-boop")
17+ ];
18+
19+ compound-expressionF = [ (spdx.compound-expression "$@#!$") ];
20+ compound-expressionP = [
21+ (spdx.compound-expression "blah")
22+ (spdx.compound-expression "blah+")
23+ (spdx.compound-expression "LicenseRef-blah")
24+ (spdx.compound-expression "DocumentRef-beep:LicenseRef-boop")
25+ (spdx.compound-expression "(blah)")
26+ (spdx.compound-expression "beep OR boop")
27+ (spdx.compound-expression "beep AND boop")
28+ (spdx.compound-expression "beep WITH boop")
29+ (spdx.compound-expression "beep AND (boop OR blap)")
30+ (spdx.compound-expression "(beep AND ((boop OR ((blap)))))")
31+ ];
32+}
modules/configuration-nix.nix+7
@@ -15,4 +15,11 @@
1515 pkgs.xorg.libXScrnSaver
1616 pkgs.xorg.libXinerama
1717 ];
18+
19+ # These packages have `license: LGPL` in their .cabal file, but
20+ # do not specify the version. Setting the version here on
21+ # examination of the license files included in the packages.
22+ packages.hscolour.package.license = pkgs.lib.mkForce "LGPL-2.1-only";
23+ packages.cpphs.package.license = pkgs.lib.mkForce "LGPL-2.1-only";
24+ packages.polyparse.package.license = pkgs.lib.mkForce "LGPL-2.1-only";
1825}