license.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # vi:sts=4:sw=4:et
  2. """Code for parsing OpenEmbedded license strings"""
  3. import ast
  4. import re
  5. from fnmatch import fnmatchcase as fnmatch
  6. def license_ok(license, dont_want_licenses):
  7. """ Return False if License exist in dont_want_licenses else True """
  8. for dwl in dont_want_licenses:
  9. # If you want to exclude license named generically 'X', we
  10. # surely want to exclude 'X+' as well. In consequence, we
  11. # will exclude a trailing '+' character from LICENSE in
  12. # case INCOMPATIBLE_LICENSE is not a 'X+' license.
  13. lic = license
  14. if not re.search('\+$', dwl):
  15. lic = re.sub('\+', '', license)
  16. if fnmatch(lic, dwl):
  17. return False
  18. return True
  19. class LicenseError(Exception):
  20. pass
  21. class LicenseSyntaxError(LicenseError):
  22. def __init__(self, licensestr, exc):
  23. self.licensestr = licensestr
  24. self.exc = exc
  25. LicenseError.__init__(self)
  26. def __str__(self):
  27. return "error in '%s': %s" % (self.licensestr, self.exc)
  28. class InvalidLicense(LicenseError):
  29. def __init__(self, license):
  30. self.license = license
  31. LicenseError.__init__(self)
  32. def __str__(self):
  33. return "invalid characters in license '%s'" % self.license
  34. license_operator_chars = '&|() '
  35. license_operator = re.compile('([' + license_operator_chars + '])')
  36. license_pattern = re.compile('[a-zA-Z0-9.+_\-]+$')
  37. class LicenseVisitor(ast.NodeVisitor):
  38. """Syntax tree visitor which can accept OpenEmbedded license strings"""
  39. def visit_string(self, licensestr):
  40. new_elements = []
  41. elements = filter(lambda x: x.strip(), license_operator.split(licensestr))
  42. for pos, element in enumerate(elements):
  43. if license_pattern.match(element):
  44. if pos > 0 and license_pattern.match(elements[pos-1]):
  45. new_elements.append('&')
  46. element = '"' + element + '"'
  47. elif not license_operator.match(element):
  48. raise InvalidLicense(element)
  49. new_elements.append(element)
  50. self.visit(ast.parse(' '.join(new_elements)))
  51. class FlattenVisitor(LicenseVisitor):
  52. """Flatten a license tree (parsed from a string) by selecting one of each
  53. set of OR options, in the way the user specifies"""
  54. def __init__(self, choose_licenses):
  55. self.choose_licenses = choose_licenses
  56. self.licenses = []
  57. LicenseVisitor.__init__(self)
  58. def visit_Str(self, node):
  59. self.licenses.append(node.s)
  60. def visit_BinOp(self, node):
  61. if isinstance(node.op, ast.BitOr):
  62. left = FlattenVisitor(self.choose_licenses)
  63. left.visit(node.left)
  64. right = FlattenVisitor(self.choose_licenses)
  65. right.visit(node.right)
  66. selected = self.choose_licenses(left.licenses, right.licenses)
  67. self.licenses.extend(selected)
  68. else:
  69. self.generic_visit(node)
  70. def flattened_licenses(licensestr, choose_licenses):
  71. """Given a license string and choose_licenses function, return a flat list of licenses"""
  72. flatten = FlattenVisitor(choose_licenses)
  73. try:
  74. flatten.visit_string(licensestr)
  75. except SyntaxError as exc:
  76. raise LicenseSyntaxError(licensestr, exc)
  77. return flatten.licenses
  78. def is_included(licensestr, whitelist=None, blacklist=None):
  79. """Given a license string and whitelist and blacklist, determine if the
  80. license string matches the whitelist and does not match the blacklist.
  81. Returns a tuple holding the boolean state and a list of the applicable
  82. licenses which were excluded (or None, if the state is True)
  83. """
  84. def include_license(license):
  85. return any(fnmatch(license, pattern) for pattern in whitelist)
  86. def exclude_license(license):
  87. return any(fnmatch(license, pattern) for pattern in blacklist)
  88. def choose_licenses(alpha, beta):
  89. """Select the option in an OR which is the 'best' (has the most
  90. included licenses)."""
  91. alpha_weight = len(filter(include_license, alpha))
  92. beta_weight = len(filter(include_license, beta))
  93. if alpha_weight > beta_weight:
  94. return alpha
  95. else:
  96. return beta
  97. if not whitelist:
  98. whitelist = ['*']
  99. if not blacklist:
  100. blacklist = []
  101. licenses = flattened_licenses(licensestr, choose_licenses)
  102. excluded = filter(lambda lic: exclude_license(lic), licenses)
  103. included = filter(lambda lic: include_license(lic), licenses)
  104. if excluded:
  105. return False, excluded
  106. else:
  107. return True, included