spdx.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #
  2. # Copyright OpenEmbedded Contributors
  3. #
  4. # SPDX-License-Identifier: GPL-2.0-only
  5. #
  6. #
  7. # This library is intended to capture the JSON SPDX specification in a type
  8. # safe manner. It is not intended to encode any particular OE specific
  9. # behaviors, see the sbom.py for that.
  10. #
  11. # The documented SPDX spec document doesn't cover the JSON syntax for
  12. # particular configuration, which can make it hard to determine what the JSON
  13. # syntax should be. I've found it is actually much simpler to read the official
  14. # SPDX JSON schema which can be found here: https://github.com/spdx/spdx-spec
  15. # in schemas/spdx-schema.json
  16. #
  17. import hashlib
  18. import itertools
  19. import json
  20. SPDX_VERSION = "2.2"
  21. #
  22. # The following are the support classes that are used to implement SPDX object
  23. #
  24. class _Property(object):
  25. """
  26. A generic SPDX object property. The different types will derive from this
  27. class
  28. """
  29. def __init__(self, *, default=None):
  30. self.default = default
  31. def setdefault(self, dest, name):
  32. if self.default is not None:
  33. dest.setdefault(name, self.default)
  34. class _String(_Property):
  35. """
  36. A scalar string property for an SPDX object
  37. """
  38. def __init__(self, **kwargs):
  39. super().__init__(**kwargs)
  40. def set_property(self, attrs, name):
  41. def get_helper(obj):
  42. return obj._spdx[name]
  43. def set_helper(obj, value):
  44. obj._spdx[name] = value
  45. def del_helper(obj):
  46. del obj._spdx[name]
  47. attrs[name] = property(get_helper, set_helper, del_helper)
  48. def init(self, source):
  49. return source
  50. class _Object(_Property):
  51. """
  52. A scalar SPDX object property of a SPDX object
  53. """
  54. def __init__(self, cls, **kwargs):
  55. super().__init__(**kwargs)
  56. self.cls = cls
  57. def set_property(self, attrs, name):
  58. def get_helper(obj):
  59. if not name in obj._spdx:
  60. obj._spdx[name] = self.cls()
  61. return obj._spdx[name]
  62. def set_helper(obj, value):
  63. obj._spdx[name] = value
  64. def del_helper(obj):
  65. del obj._spdx[name]
  66. attrs[name] = property(get_helper, set_helper)
  67. def init(self, source):
  68. return self.cls(**source)
  69. class _ListProperty(_Property):
  70. """
  71. A list of SPDX properties
  72. """
  73. def __init__(self, prop, **kwargs):
  74. super().__init__(**kwargs)
  75. self.prop = prop
  76. def set_property(self, attrs, name):
  77. def get_helper(obj):
  78. if not name in obj._spdx:
  79. obj._spdx[name] = []
  80. return obj._spdx[name]
  81. def set_helper(obj, value):
  82. obj._spdx[name] = list(value)
  83. def del_helper(obj):
  84. del obj._spdx[name]
  85. attrs[name] = property(get_helper, set_helper, del_helper)
  86. def init(self, source):
  87. return [self.prop.init(o) for o in source]
  88. class _StringList(_ListProperty):
  89. """
  90. A list of strings as a property for an SPDX object
  91. """
  92. def __init__(self, **kwargs):
  93. super().__init__(_String(), **kwargs)
  94. class _ObjectList(_ListProperty):
  95. """
  96. A list of SPDX objects as a property for an SPDX object
  97. """
  98. def __init__(self, cls, **kwargs):
  99. super().__init__(_Object(cls), **kwargs)
  100. class MetaSPDXObject(type):
  101. """
  102. A metaclass that allows properties (anything derived from a _Property
  103. class) to be defined for a SPDX object
  104. """
  105. def __new__(mcls, name, bases, attrs):
  106. attrs["_properties"] = {}
  107. for key in attrs.keys():
  108. if isinstance(attrs[key], _Property):
  109. prop = attrs[key]
  110. attrs["_properties"][key] = prop
  111. prop.set_property(attrs, key)
  112. return super().__new__(mcls, name, bases, attrs)
  113. class SPDXObject(metaclass=MetaSPDXObject):
  114. """
  115. The base SPDX object; all SPDX spec classes must derive from this class
  116. """
  117. def __init__(self, **d):
  118. self._spdx = {}
  119. for name, prop in self._properties.items():
  120. prop.setdefault(self._spdx, name)
  121. if name in d:
  122. self._spdx[name] = prop.init(d[name])
  123. def serializer(self):
  124. return self._spdx
  125. def __setattr__(self, name, value):
  126. if name in self._properties or name == "_spdx":
  127. super().__setattr__(name, value)
  128. return
  129. raise KeyError("%r is not a valid SPDX property" % name)
  130. #
  131. # These are the SPDX objects implemented from the spec. The *only* properties
  132. # that can be added to these objects are ones directly specified in the SPDX
  133. # spec, however you may add helper functions to make operations easier.
  134. #
  135. # Defaults should *only* be specified if the SPDX spec says there is a certain
  136. # required value for a field (e.g. dataLicense), or if the field is mandatory
  137. # and has some sane "this field is unknown" (e.g. "NOASSERTION")
  138. #
  139. class SPDXAnnotation(SPDXObject):
  140. annotationDate = _String()
  141. annotationType = _String()
  142. annotator = _String()
  143. comment = _String()
  144. class SPDXChecksum(SPDXObject):
  145. algorithm = _String()
  146. checksumValue = _String()
  147. class SPDXRelationship(SPDXObject):
  148. spdxElementId = _String()
  149. relatedSpdxElement = _String()
  150. relationshipType = _String()
  151. comment = _String()
  152. annotations = _ObjectList(SPDXAnnotation)
  153. class SPDXExternalReference(SPDXObject):
  154. referenceCategory = _String()
  155. referenceType = _String()
  156. referenceLocator = _String()
  157. class SPDXPackageVerificationCode(SPDXObject):
  158. packageVerificationCodeValue = _String()
  159. packageVerificationCodeExcludedFiles = _StringList()
  160. class SPDXPackage(SPDXObject):
  161. ALLOWED_CHECKSUMS = [
  162. "SHA1",
  163. "SHA224",
  164. "SHA256",
  165. "SHA384",
  166. "SHA512",
  167. "MD2",
  168. "MD4",
  169. "MD5",
  170. "MD6",
  171. ]
  172. name = _String()
  173. SPDXID = _String()
  174. versionInfo = _String()
  175. downloadLocation = _String(default="NOASSERTION")
  176. supplier = _String(default="NOASSERTION")
  177. homepage = _String()
  178. licenseConcluded = _String(default="NOASSERTION")
  179. licenseDeclared = _String(default="NOASSERTION")
  180. summary = _String()
  181. description = _String()
  182. sourceInfo = _String()
  183. copyrightText = _String(default="NOASSERTION")
  184. licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
  185. externalRefs = _ObjectList(SPDXExternalReference)
  186. packageVerificationCode = _Object(SPDXPackageVerificationCode)
  187. hasFiles = _StringList()
  188. packageFileName = _String()
  189. annotations = _ObjectList(SPDXAnnotation)
  190. checksums = _ObjectList(SPDXChecksum)
  191. class SPDXFile(SPDXObject):
  192. SPDXID = _String()
  193. fileName = _String()
  194. licenseConcluded = _String(default="NOASSERTION")
  195. copyrightText = _String(default="NOASSERTION")
  196. licenseInfoInFiles = _StringList(default=["NOASSERTION"])
  197. checksums = _ObjectList(SPDXChecksum)
  198. fileTypes = _StringList()
  199. class SPDXCreationInfo(SPDXObject):
  200. created = _String()
  201. licenseListVersion = _String()
  202. comment = _String()
  203. creators = _StringList()
  204. class SPDXExternalDocumentRef(SPDXObject):
  205. externalDocumentId = _String()
  206. spdxDocument = _String()
  207. checksum = _Object(SPDXChecksum)
  208. class SPDXExtractedLicensingInfo(SPDXObject):
  209. name = _String()
  210. comment = _String()
  211. licenseId = _String()
  212. extractedText = _String()
  213. class SPDXDocument(SPDXObject):
  214. spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
  215. dataLicense = _String(default="CC0-1.0")
  216. SPDXID = _String(default="SPDXRef-DOCUMENT")
  217. name = _String()
  218. documentNamespace = _String()
  219. creationInfo = _Object(SPDXCreationInfo)
  220. packages = _ObjectList(SPDXPackage)
  221. files = _ObjectList(SPDXFile)
  222. relationships = _ObjectList(SPDXRelationship)
  223. externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
  224. hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
  225. def __init__(self, **d):
  226. super().__init__(**d)
  227. def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
  228. class Encoder(json.JSONEncoder):
  229. def default(self, o):
  230. if isinstance(o, SPDXObject):
  231. return o.serializer()
  232. return super().default(o)
  233. sha1 = hashlib.sha1()
  234. for chunk in Encoder(
  235. sort_keys=sort_keys,
  236. indent=indent,
  237. separators=separators,
  238. ).iterencode(self):
  239. chunk = chunk.encode("utf-8")
  240. f.write(chunk)
  241. sha1.update(chunk)
  242. return sha1.hexdigest()
  243. @classmethod
  244. def from_json(cls, f):
  245. return cls(**json.load(f))
  246. def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
  247. if isinstance(_from, SPDXObject):
  248. from_spdxid = _from.SPDXID
  249. else:
  250. from_spdxid = _from
  251. if isinstance(_to, SPDXObject):
  252. to_spdxid = _to.SPDXID
  253. else:
  254. to_spdxid = _to
  255. r = SPDXRelationship(
  256. spdxElementId=from_spdxid,
  257. relatedSpdxElement=to_spdxid,
  258. relationshipType=relationship,
  259. )
  260. if comment is not None:
  261. r.comment = comment
  262. if annotation is not None:
  263. r.annotations.append(annotation)
  264. self.relationships.append(r)
  265. def find_by_spdxid(self, spdxid):
  266. for o in itertools.chain(self.packages, self.files):
  267. if o.SPDXID == spdxid:
  268. return o
  269. return None
  270. def find_external_document_ref(self, namespace):
  271. for r in self.externalDocumentRefs:
  272. if r.spdxDocument == namespace:
  273. return r
  274. return None