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 17class LicenseError(Exception): 18 pass 19 20class LicenseSyntaxError(LicenseError): 21 def __init__(self, licensestr, exc): 22 self.licensestr = licensestr 23 self.exc = exc 24 LicenseError.__init__(self) 25 26 def __str__(self): 27 return "error in '%s': %s" % (self.licensestr, self.exc) 28 29class InvalidLicense(LicenseError): 30 def __init__(self, license): 31 self.license = license 32 LicenseError.__init__(self) 33 34 def __str__(self): 35 return "invalid characters in license '%s'" % self.license 36 37license_operator_chars = '&|() ' 38license_operator = re.compile(r'([' + license_operator_chars + '])') 39license_pattern = re.compile(r'[a-zA-Z0-9.+_\-]+$') 40 41class LicenseVisitor(ast.NodeVisitor): 42 """Get elements based on OpenEmbedded license strings""" 43 def get_elements(self, licensestr): 44 new_elements = [] 45 elements = list([x for x in license_operator.split(licensestr) if x.strip()]) 46 for pos, element in enumerate(elements): 47 if license_pattern.match(element): 48 if pos > 0 and license_pattern.match(elements[pos-1]): 49 new_elements.append('&') 50 element = '"' + element + '"' 51 elif not license_operator.match(element): 52 raise InvalidLicense(element) 53 new_elements.append(element) 54 55 return new_elements 56 57 """Syntax tree visitor which can accept elements previously generated with 58 OpenEmbedded license string""" 59 def visit_elements(self, elements): 60 self.visit(ast.parse(' '.join(elements))) 61 62 """Syntax tree visitor which can accept OpenEmbedded license strings""" 63 def visit_string(self, licensestr): 64 self.visit_elements(self.get_elements(licensestr)) 65 66class FlattenVisitor(LicenseVisitor): 67 """Flatten a license tree (parsed from a string) by selecting one of each 68 set of OR options, in the way the user specifies""" 69 def __init__(self, choose_licenses): 70 self.choose_licenses = choose_licenses 71 self.licenses = [] 72 LicenseVisitor.__init__(self) 73 74 def visit_Str(self, node): 75 self.licenses.append(node.s) 76 77 def visit_BinOp(self, node): 78 if isinstance(node.op, ast.BitOr): 79 left = FlattenVisitor(self.choose_licenses) 80 left.visit(node.left) 81 82 right = FlattenVisitor(self.choose_licenses) 83 right.visit(node.right) 84 85 selected = self.choose_licenses(left.licenses, right.licenses) 86 self.licenses.extend(selected) 87 else: 88 self.generic_visit(node) 89 90def flattened_licenses(licensestr, choose_licenses): 91 """Given a license string and choose_licenses function, return a flat list of licenses""" 92 flatten = FlattenVisitor(choose_licenses) 93 try: 94 flatten.visit_string(licensestr) 95 except SyntaxError as exc: 96 raise LicenseSyntaxError(licensestr, exc) 97 return flatten.licenses 98 99def is_included(licensestr, whitelist=None, blacklist=None): 100 """Given a license string and whitelist and blacklist, determine if the 101 license string matches the whitelist and does not match the blacklist. 102 103 Returns a tuple holding the boolean state and a list of the applicable 104 licenses that were excluded if state is False, or the licenses that were 105 included if the state is True. 106 """ 107 108 def include_license(license): 109 return any(fnmatch(license, pattern) for pattern in whitelist) 110 111 def exclude_license(license): 112 return any(fnmatch(license, pattern) for pattern in blacklist) 113 114 def choose_licenses(alpha, beta): 115 """Select the option in an OR which is the 'best' (has the most 116 included licenses and no excluded licenses).""" 117 # The factor 1000 below is arbitrary, just expected to be much larger 118 # that the number of licenses actually specified. That way the weight 119 # will be negative if the list of licenses contains an excluded license, 120 # but still gives a higher weight to the list with the most included 121 # licenses. 122 alpha_weight = (len(list(filter(include_license, alpha))) - 123 1000 * (len(list(filter(exclude_license, alpha))) > 0)) 124 beta_weight = (len(list(filter(include_license, beta))) - 125 1000 * (len(list(filter(exclude_license, beta))) > 0)) 126 if alpha_weight >= beta_weight: 127 return alpha 128 else: 129 return beta 130 131 if not whitelist: 132 whitelist = ['*'] 133 134 if not blacklist: 135 blacklist = [] 136 137 licenses = flattened_licenses(licensestr, choose_licenses) 138 excluded = [lic for lic in licenses if exclude_license(lic)] 139 included = [lic for lic in licenses if include_license(lic)] 140 if excluded: 141 return False, excluded 142 else: 143 return True, included 144 145class ManifestVisitor(LicenseVisitor): 146 """Walk license tree (parsed from a string) removing the incompatible 147 licenses specified""" 148 def __init__(self, dont_want_licenses, canonical_license, d): 149 self._dont_want_licenses = dont_want_licenses 150 self._canonical_license = canonical_license 151 self._d = d 152 self._operators = [] 153 154 self.licenses = [] 155 self.licensestr = '' 156 157 LicenseVisitor.__init__(self) 158 159 def visit(self, node): 160 if isinstance(node, ast.Str): 161 lic = node.s 162 163 if license_ok(self._canonical_license(self._d, lic), 164 self._dont_want_licenses) == True: 165 if self._operators: 166 ops = [] 167 for op in self._operators: 168 if op == '[': 169 ops.append(op) 170 elif op == ']': 171 ops.append(op) 172 else: 173 if not ops: 174 ops.append(op) 175 elif ops[-1] in ['[', ']']: 176 ops.append(op) 177 else: 178 ops[-1] = op 179 180 for op in ops: 181 if op == '[' or op == ']': 182 self.licensestr += op 183 elif self.licenses: 184 self.licensestr += ' ' + op + ' ' 185 186 self._operators = [] 187 188 self.licensestr += lic 189 self.licenses.append(lic) 190 elif isinstance(node, ast.BitAnd): 191 self._operators.append("&") 192 elif isinstance(node, ast.BitOr): 193 self._operators.append("|") 194 elif isinstance(node, ast.List): 195 self._operators.append("[") 196 elif isinstance(node, ast.Load): 197 self.licensestr += "]" 198 199 self.generic_visit(node) 200 201def manifest_licenses(licensestr, dont_want_licenses, canonical_license, d): 202 """Given a license string and dont_want_licenses list, 203 return license string filtered and a list of licenses""" 204 manifest = ManifestVisitor(dont_want_licenses, canonical_license, d) 205 206 try: 207 elements = manifest.get_elements(licensestr) 208 209 # Replace '()' to '[]' for handle in ast as List and Load types. 210 elements = ['[' if e == '(' else e for e in elements] 211 elements = [']' if e == ')' else e for e in elements] 212 213 manifest.visit_elements(elements) 214 except SyntaxError as exc: 215 raise LicenseSyntaxError(licensestr, exc) 216 217 # Replace '[]' to '()' for output correct license. 218 manifest.licensestr = manifest.licensestr.replace('[', '(').replace(']', ')') 219 220 return (manifest.licensestr, manifest.licenses) 221 222class ListVisitor(LicenseVisitor): 223 """Record all different licenses found in the license string""" 224 def __init__(self): 225 self.licenses = set() 226 227 def visit_Str(self, node): 228 self.licenses.add(node.s) 229 230def list_licenses(licensestr): 231 """Simply get a list of all licenses mentioned in a license string. 232 Binary operators are not applied or taken into account in any way""" 233 visitor = ListVisitor() 234 try: 235 visitor.visit_string(licensestr) 236 except SyntaxError as exc: 237 raise LicenseSyntaxError(licensestr, exc) 238 return visitor.licenses 239