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