license.py 8.8 KB

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