blob: 99cfa5f7339d0fe48572c9cacbafd6231e0eb169 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004"""Code for parsing OpenEmbedded license strings"""
5
6import ast
7import re
8from fnmatch import fnmatchcase as fnmatch
9
10def license_ok(license, dont_want_licenses):
11 """ Return False if License exist in dont_want_licenses else True """
12 for dwl in dont_want_licenses:
Andrew Geissler90fd73c2021-03-05 15:25:55 -060013 if fnmatch(license, dwl):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014 return False
15 return True
16
Andrew Geissler9aee5002022-03-30 16:27:02 +000017def obsolete_license_list():
18 return ["AGPL-3", "AGPL-3+", "AGPLv3", "AGPLv3+", "AGPLv3.0", "AGPLv3.0+", "AGPL-3.0", "AGPL-3.0+", "BSD-0-Clause",
19 "GPL-1", "GPL-1+", "GPLv1", "GPLv1+", "GPLv1.0", "GPLv1.0+", "GPL-1.0", "GPL-1.0+", "GPL-2", "GPL-2+", "GPLv2",
20 "GPLv2+", "GPLv2.0", "GPLv2.0+", "GPL-2.0", "GPL-2.0+", "GPL-3", "GPL-3+", "GPLv3", "GPLv3+", "GPLv3.0", "GPLv3.0+",
21 "GPL-3.0", "GPL-3.0+", "LGPLv2", "LGPLv2+", "LGPLv2.0", "LGPLv2.0+", "LGPL-2.0", "LGPL-2.0+", "LGPL2.1", "LGPL2.1+",
22 "LGPLv2.1", "LGPLv2.1+", "LGPL-2.1", "LGPL-2.1+", "LGPLv3", "LGPLv3+", "LGPL-3.0", "LGPL-3.0+", "MPL-1", "MPLv1",
23 "MPLv1.1", "MPLv2", "MIT-X", "MIT-style", "openssl", "PSF", "PSFv2", "Python-2", "Apachev2", "Apache-2", "Artisticv1",
24 "Artistic-1", "AFL-2", "AFL-1", "AFLv2", "AFLv1", "CDDLv1", "CDDL-1", "EPLv1.0", "FreeType", "Nauman",
25 "tcl", "vim", "SGIv1"]
26
Patrick Williamsc124f4f2015-09-15 14:41:29 -050027class LicenseError(Exception):
28 pass
29
30class LicenseSyntaxError(LicenseError):
31 def __init__(self, licensestr, exc):
32 self.licensestr = licensestr
33 self.exc = exc
34 LicenseError.__init__(self)
35
36 def __str__(self):
37 return "error in '%s': %s" % (self.licensestr, self.exc)
38
39class InvalidLicense(LicenseError):
40 def __init__(self, license):
41 self.license = license
42 LicenseError.__init__(self)
43
44 def __str__(self):
45 return "invalid characters in license '%s'" % self.license
46
47license_operator_chars = '&|() '
Brad Bishop19323692019-04-05 15:28:33 -040048license_operator = re.compile(r'([' + license_operator_chars + '])')
49license_pattern = re.compile(r'[a-zA-Z0-9.+_\-]+$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050
51class LicenseVisitor(ast.NodeVisitor):
52 """Get elements based on OpenEmbedded license strings"""
53 def get_elements(self, licensestr):
54 new_elements = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -060055 elements = list([x for x in license_operator.split(licensestr) if x.strip()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050056 for pos, element in enumerate(elements):
57 if license_pattern.match(element):
58 if pos > 0 and license_pattern.match(elements[pos-1]):
59 new_elements.append('&')
60 element = '"' + element + '"'
61 elif not license_operator.match(element):
62 raise InvalidLicense(element)
63 new_elements.append(element)
64
65 return new_elements
66
67 """Syntax tree visitor which can accept elements previously generated with
68 OpenEmbedded license string"""
69 def visit_elements(self, elements):
70 self.visit(ast.parse(' '.join(elements)))
71
72 """Syntax tree visitor which can accept OpenEmbedded license strings"""
73 def visit_string(self, licensestr):
74 self.visit_elements(self.get_elements(licensestr))
75
76class FlattenVisitor(LicenseVisitor):
77 """Flatten a license tree (parsed from a string) by selecting one of each
78 set of OR options, in the way the user specifies"""
79 def __init__(self, choose_licenses):
80 self.choose_licenses = choose_licenses
81 self.licenses = []
82 LicenseVisitor.__init__(self)
83
84 def visit_Str(self, node):
85 self.licenses.append(node.s)
86
Andrew Geisslereff27472021-10-29 15:35:00 -050087 def visit_Constant(self, node):
88 self.licenses.append(node.value)
89
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 def visit_BinOp(self, node):
91 if isinstance(node.op, ast.BitOr):
92 left = FlattenVisitor(self.choose_licenses)
93 left.visit(node.left)
94
95 right = FlattenVisitor(self.choose_licenses)
96 right.visit(node.right)
97
98 selected = self.choose_licenses(left.licenses, right.licenses)
99 self.licenses.extend(selected)
100 else:
101 self.generic_visit(node)
102
103def flattened_licenses(licensestr, choose_licenses):
104 """Given a license string and choose_licenses function, return a flat list of licenses"""
105 flatten = FlattenVisitor(choose_licenses)
106 try:
107 flatten.visit_string(licensestr)
108 except SyntaxError as exc:
109 raise LicenseSyntaxError(licensestr, exc)
110 return flatten.licenses
111
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000112def is_included(licensestr, include_licenses=None, exclude_licenses=None):
Andrew Geissler9aee5002022-03-30 16:27:02 +0000113 """Given a license string, a list of licenses to include and a list of
114 licenses to exclude, determine if the license string matches the include
115 list and does not match the exclude list.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116
Andrew Geissler9aee5002022-03-30 16:27:02 +0000117 Returns a tuple holding the boolean state and a list of the applicable
118 licenses that were excluded if state is False, or the licenses that were
119 included if the state is True."""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120
121 def include_license(license):
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000122 return any(fnmatch(license, pattern) for pattern in include_licenses)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500123
124 def exclude_license(license):
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000125 return any(fnmatch(license, pattern) for pattern in exclude_licenses)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500126
127 def choose_licenses(alpha, beta):
128 """Select the option in an OR which is the 'best' (has the most
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500129 included licenses and no excluded licenses)."""
130 # The factor 1000 below is arbitrary, just expected to be much larger
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000131 # than the number of licenses actually specified. That way the weight
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500132 # will be negative if the list of licenses contains an excluded license,
133 # but still gives a higher weight to the list with the most included
134 # licenses.
135 alpha_weight = (len(list(filter(include_license, alpha))) -
136 1000 * (len(list(filter(exclude_license, alpha))) > 0))
137 beta_weight = (len(list(filter(include_license, beta))) -
138 1000 * (len(list(filter(exclude_license, beta))) > 0))
139 if alpha_weight >= beta_weight:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500140 return alpha
141 else:
142 return beta
143
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000144 if not include_licenses:
145 include_licenses = ['*']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000147 if not exclude_licenses:
148 exclude_licenses = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149
150 licenses = flattened_licenses(licensestr, choose_licenses)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600151 excluded = [lic for lic in licenses if exclude_license(lic)]
152 included = [lic for lic in licenses if include_license(lic)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500153 if excluded:
154 return False, excluded
155 else:
156 return True, included
157
158class ManifestVisitor(LicenseVisitor):
159 """Walk license tree (parsed from a string) removing the incompatible
160 licenses specified"""
161 def __init__(self, dont_want_licenses, canonical_license, d):
162 self._dont_want_licenses = dont_want_licenses
163 self._canonical_license = canonical_license
164 self._d = d
165 self._operators = []
166
167 self.licenses = []
168 self.licensestr = ''
169
170 LicenseVisitor.__init__(self)
171
172 def visit(self, node):
173 if isinstance(node, ast.Str):
174 lic = node.s
175
176 if license_ok(self._canonical_license(self._d, lic),
177 self._dont_want_licenses) == True:
178 if self._operators:
179 ops = []
180 for op in self._operators:
181 if op == '[':
182 ops.append(op)
183 elif op == ']':
184 ops.append(op)
185 else:
186 if not ops:
187 ops.append(op)
188 elif ops[-1] in ['[', ']']:
189 ops.append(op)
190 else:
191 ops[-1] = op
192
193 for op in ops:
194 if op == '[' or op == ']':
195 self.licensestr += op
196 elif self.licenses:
197 self.licensestr += ' ' + op + ' '
198
199 self._operators = []
200
201 self.licensestr += lic
202 self.licenses.append(lic)
203 elif isinstance(node, ast.BitAnd):
204 self._operators.append("&")
205 elif isinstance(node, ast.BitOr):
206 self._operators.append("|")
207 elif isinstance(node, ast.List):
208 self._operators.append("[")
209 elif isinstance(node, ast.Load):
210 self.licensestr += "]"
211
212 self.generic_visit(node)
213
214def manifest_licenses(licensestr, dont_want_licenses, canonical_license, d):
215 """Given a license string and dont_want_licenses list,
216 return license string filtered and a list of licenses"""
217 manifest = ManifestVisitor(dont_want_licenses, canonical_license, d)
218
219 try:
220 elements = manifest.get_elements(licensestr)
221
222 # Replace '()' to '[]' for handle in ast as List and Load types.
223 elements = ['[' if e == '(' else e for e in elements]
224 elements = [']' if e == ')' else e for e in elements]
225
226 manifest.visit_elements(elements)
227 except SyntaxError as exc:
228 raise LicenseSyntaxError(licensestr, exc)
229
230 # Replace '[]' to '()' for output correct license.
231 manifest.licensestr = manifest.licensestr.replace('[', '(').replace(']', ')')
232
233 return (manifest.licensestr, manifest.licenses)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234
235class ListVisitor(LicenseVisitor):
236 """Record all different licenses found in the license string"""
237 def __init__(self):
238 self.licenses = set()
239
240 def visit_Str(self, node):
241 self.licenses.add(node.s)
242
Andrew Geisslereff27472021-10-29 15:35:00 -0500243 def visit_Constant(self, node):
244 self.licenses.add(node.value)
245
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600246def list_licenses(licensestr):
247 """Simply get a list of all licenses mentioned in a license string.
248 Binary operators are not applied or taken into account in any way"""
249 visitor = ListVisitor()
250 try:
251 visitor.visit_string(licensestr)
252 except SyntaxError as exc:
253 raise LicenseSyntaxError(licensestr, exc)
254 return visitor.licenses
Andrew Geissler9aee5002022-03-30 16:27:02 +0000255
256def apply_pkg_license_exception(pkg, bad_licenses, exceptions):
257 """Return remaining bad licenses after removing any package exceptions"""
258
259 return [lic for lic in bad_licenses if pkg + ':' + lic not in exceptions]