xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oe/license.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4"""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:
13        if fnmatch(license, dwl):
14            return False
15    return True
16
17def 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
27class 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 = '&|() '
48license_operator = re.compile(r'([' + license_operator_chars + '])')
49license_pattern = re.compile(r'[a-zA-Z0-9.+_\-]+$')
50
51class LicenseVisitor(ast.NodeVisitor):
52    """Get elements based on OpenEmbedded license strings"""
53    def get_elements(self, licensestr):
54        new_elements = []
55        elements = list([x for x in license_operator.split(licensestr) if x.strip()])
56        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
87    def visit_Constant(self, node):
88        self.licenses.append(node.value)
89
90    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
112def is_included(licensestr, include_licenses=None, exclude_licenses=None):
113    """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.
116
117    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."""
120
121    def include_license(license):
122        return any(fnmatch(license, pattern) for pattern in include_licenses)
123
124    def exclude_license(license):
125        return any(fnmatch(license, pattern) for pattern in exclude_licenses)
126
127    def choose_licenses(alpha, beta):
128        """Select the option in an OR which is the 'best' (has the most
129        included licenses and no excluded licenses)."""
130        # The factor 1000 below is arbitrary, just expected to be much larger
131        # than the number of licenses actually specified. That way the weight
132        # 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:
140            return alpha
141        else:
142            return beta
143
144    if not include_licenses:
145        include_licenses = ['*']
146
147    if not exclude_licenses:
148        exclude_licenses = []
149
150    licenses = flattened_licenses(licensestr, choose_licenses)
151    excluded = [lic for lic in licenses if exclude_license(lic)]
152    included = [lic for lic in licenses if include_license(lic)]
153    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)
234
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
243    def visit_Constant(self, node):
244        self.licenses.add(node.value)
245
246def 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
255
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]
260