blob: 7aaf2af5edd880489a95969893f93a8e764170ee [file] [log] [blame]
Andrew Geissler5199d832021-09-24 16:47:35 -05001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright OpenEmbedded Contributors
3#
Andrew Geissler5199d832021-09-24 16:47:35 -05004# SPDX-License-Identifier: GPL-2.0-only
5#
6
Patrick Williams93c203f2021-10-06 16:15:23 -05007#
8# This library is intended to capture the JSON SPDX specification in a type
9# safe manner. It is not intended to encode any particular OE specific
10# behaviors, see the sbom.py for that.
11#
12# The documented SPDX spec document doesn't cover the JSON syntax for
13# particular configuration, which can make it hard to determine what the JSON
14# syntax should be. I've found it is actually much simpler to read the official
15# SPDX JSON schema which can be found here: https://github.com/spdx/spdx-spec
16# in schemas/spdx-schema.json
17#
18
Andrew Geissler5199d832021-09-24 16:47:35 -050019import hashlib
20import itertools
21import json
22
23SPDX_VERSION = "2.2"
24
25
Patrick Williams93c203f2021-10-06 16:15:23 -050026#
27# The following are the support classes that are used to implement SPDX object
28#
29
Andrew Geissler5199d832021-09-24 16:47:35 -050030class _Property(object):
Patrick Williams93c203f2021-10-06 16:15:23 -050031 """
32 A generic SPDX object property. The different types will derive from this
33 class
34 """
35
Andrew Geissler5199d832021-09-24 16:47:35 -050036 def __init__(self, *, default=None):
37 self.default = default
38
39 def setdefault(self, dest, name):
40 if self.default is not None:
41 dest.setdefault(name, self.default)
42
43
44class _String(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050045 """
46 A scalar string property for an SPDX object
47 """
48
Andrew Geissler5199d832021-09-24 16:47:35 -050049 def __init__(self, **kwargs):
50 super().__init__(**kwargs)
51
52 def set_property(self, attrs, name):
53 def get_helper(obj):
54 return obj._spdx[name]
55
56 def set_helper(obj, value):
57 obj._spdx[name] = value
58
59 def del_helper(obj):
60 del obj._spdx[name]
61
62 attrs[name] = property(get_helper, set_helper, del_helper)
63
64 def init(self, source):
65 return source
66
67
68class _Object(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050069 """
70 A scalar SPDX object property of a SPDX object
71 """
72
Andrew Geissler5199d832021-09-24 16:47:35 -050073 def __init__(self, cls, **kwargs):
74 super().__init__(**kwargs)
75 self.cls = cls
76
77 def set_property(self, attrs, name):
78 def get_helper(obj):
79 if not name in obj._spdx:
80 obj._spdx[name] = self.cls()
81 return obj._spdx[name]
82
83 def set_helper(obj, value):
84 obj._spdx[name] = value
85
86 def del_helper(obj):
87 del obj._spdx[name]
88
89 attrs[name] = property(get_helper, set_helper)
90
91 def init(self, source):
92 return self.cls(**source)
93
94
95class _ListProperty(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050096 """
97 A list of SPDX properties
98 """
99
Andrew Geissler5199d832021-09-24 16:47:35 -0500100 def __init__(self, prop, **kwargs):
101 super().__init__(**kwargs)
102 self.prop = prop
103
104 def set_property(self, attrs, name):
105 def get_helper(obj):
106 if not name in obj._spdx:
107 obj._spdx[name] = []
108 return obj._spdx[name]
109
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000110 def set_helper(obj, value):
111 obj._spdx[name] = list(value)
112
Andrew Geissler5199d832021-09-24 16:47:35 -0500113 def del_helper(obj):
114 del obj._spdx[name]
115
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000116 attrs[name] = property(get_helper, set_helper, del_helper)
Andrew Geissler5199d832021-09-24 16:47:35 -0500117
118 def init(self, source):
119 return [self.prop.init(o) for o in source]
120
121
122class _StringList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500123 """
124 A list of strings as a property for an SPDX object
125 """
126
Andrew Geissler5199d832021-09-24 16:47:35 -0500127 def __init__(self, **kwargs):
128 super().__init__(_String(), **kwargs)
129
130
131class _ObjectList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500132 """
133 A list of SPDX objects as a property for an SPDX object
134 """
135
Andrew Geissler5199d832021-09-24 16:47:35 -0500136 def __init__(self, cls, **kwargs):
137 super().__init__(_Object(cls), **kwargs)
138
139
140class MetaSPDXObject(type):
Patrick Williams93c203f2021-10-06 16:15:23 -0500141 """
142 A metaclass that allows properties (anything derived from a _Property
143 class) to be defined for a SPDX object
144 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500145 def __new__(mcls, name, bases, attrs):
146 attrs["_properties"] = {}
147
148 for key in attrs.keys():
149 if isinstance(attrs[key], _Property):
150 prop = attrs[key]
151 attrs["_properties"][key] = prop
152 prop.set_property(attrs, key)
153
154 return super().__new__(mcls, name, bases, attrs)
155
156
157class SPDXObject(metaclass=MetaSPDXObject):
Patrick Williams93c203f2021-10-06 16:15:23 -0500158 """
159 The base SPDX object; all SPDX spec classes must derive from this class
160 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500161 def __init__(self, **d):
162 self._spdx = {}
163
164 for name, prop in self._properties.items():
165 prop.setdefault(self._spdx, name)
166 if name in d:
167 self._spdx[name] = prop.init(d[name])
168
169 def serializer(self):
170 return self._spdx
171
172 def __setattr__(self, name, value):
173 if name in self._properties or name == "_spdx":
174 super().__setattr__(name, value)
175 return
176 raise KeyError("%r is not a valid SPDX property" % name)
177
Patrick Williams93c203f2021-10-06 16:15:23 -0500178#
179# These are the SPDX objects implemented from the spec. The *only* properties
180# that can be added to these objects are ones directly specified in the SPDX
181# spec, however you may add helper functions to make operations easier.
182#
183# Defaults should *only* be specified if the SPDX spec says there is a certain
184# required value for a field (e.g. dataLicense), or if the field is mandatory
185# and has some sane "this field is unknown" (e.g. "NOASSERTION")
186#
187
188class SPDXAnnotation(SPDXObject):
189 annotationDate = _String()
190 annotationType = _String()
191 annotator = _String()
192 comment = _String()
Andrew Geissler5199d832021-09-24 16:47:35 -0500193
194class SPDXChecksum(SPDXObject):
195 algorithm = _String()
196 checksumValue = _String()
197
198
199class SPDXRelationship(SPDXObject):
200 spdxElementId = _String()
201 relatedSpdxElement = _String()
202 relationshipType = _String()
203 comment = _String()
Andrew Geisslereff27472021-10-29 15:35:00 -0500204 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500205
206
207class SPDXExternalReference(SPDXObject):
208 referenceCategory = _String()
209 referenceType = _String()
210 referenceLocator = _String()
211
212
213class SPDXPackageVerificationCode(SPDXObject):
214 packageVerificationCodeValue = _String()
215 packageVerificationCodeExcludedFiles = _StringList()
216
217
218class SPDXPackage(SPDXObject):
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600219 ALLOWED_CHECKSUMS = [
220 "SHA1",
221 "SHA224",
222 "SHA256",
223 "SHA384",
224 "SHA512",
225 "MD2",
226 "MD4",
227 "MD5",
228 "MD6",
229 ]
230
Andrew Geissler5199d832021-09-24 16:47:35 -0500231 name = _String()
232 SPDXID = _String()
233 versionInfo = _String()
234 downloadLocation = _String(default="NOASSERTION")
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500235 supplier = _String(default="NOASSERTION")
Andrew Geissler5199d832021-09-24 16:47:35 -0500236 homepage = _String()
237 licenseConcluded = _String(default="NOASSERTION")
238 licenseDeclared = _String(default="NOASSERTION")
239 summary = _String()
240 description = _String()
241 sourceInfo = _String()
242 copyrightText = _String(default="NOASSERTION")
243 licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
244 externalRefs = _ObjectList(SPDXExternalReference)
245 packageVerificationCode = _Object(SPDXPackageVerificationCode)
246 hasFiles = _StringList()
247 packageFileName = _String()
Patrick Williams93c203f2021-10-06 16:15:23 -0500248 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600249 checksums = _ObjectList(SPDXChecksum)
Andrew Geissler5199d832021-09-24 16:47:35 -0500250
251
252class SPDXFile(SPDXObject):
253 SPDXID = _String()
254 fileName = _String()
255 licenseConcluded = _String(default="NOASSERTION")
256 copyrightText = _String(default="NOASSERTION")
257 licenseInfoInFiles = _StringList(default=["NOASSERTION"])
258 checksums = _ObjectList(SPDXChecksum)
259 fileTypes = _StringList()
260
261
262class SPDXCreationInfo(SPDXObject):
263 created = _String()
264 licenseListVersion = _String()
265 comment = _String()
266 creators = _StringList()
267
268
269class SPDXExternalDocumentRef(SPDXObject):
270 externalDocumentId = _String()
271 spdxDocument = _String()
272 checksum = _Object(SPDXChecksum)
273
274
275class SPDXExtractedLicensingInfo(SPDXObject):
276 name = _String()
277 comment = _String()
278 licenseId = _String()
279 extractedText = _String()
280
281
282class SPDXDocument(SPDXObject):
283 spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
284 dataLicense = _String(default="CC0-1.0")
285 SPDXID = _String(default="SPDXRef-DOCUMENT")
286 name = _String()
287 documentNamespace = _String()
288 creationInfo = _Object(SPDXCreationInfo)
289 packages = _ObjectList(SPDXPackage)
290 files = _ObjectList(SPDXFile)
291 relationships = _ObjectList(SPDXRelationship)
292 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
293 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
294
295 def __init__(self, **d):
296 super().__init__(**d)
297
298 def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
299 class Encoder(json.JSONEncoder):
300 def default(self, o):
301 if isinstance(o, SPDXObject):
302 return o.serializer()
303
304 return super().default(o)
305
306 sha1 = hashlib.sha1()
307 for chunk in Encoder(
308 sort_keys=sort_keys,
309 indent=indent,
310 separators=separators,
311 ).iterencode(self):
312 chunk = chunk.encode("utf-8")
313 f.write(chunk)
314 sha1.update(chunk)
315
316 return sha1.hexdigest()
317
318 @classmethod
319 def from_json(cls, f):
320 return cls(**json.load(f))
321
Andrew Geisslereff27472021-10-29 15:35:00 -0500322 def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
Andrew Geissler5199d832021-09-24 16:47:35 -0500323 if isinstance(_from, SPDXObject):
324 from_spdxid = _from.SPDXID
325 else:
326 from_spdxid = _from
327
328 if isinstance(_to, SPDXObject):
329 to_spdxid = _to.SPDXID
330 else:
331 to_spdxid = _to
332
333 r = SPDXRelationship(
334 spdxElementId=from_spdxid,
335 relatedSpdxElement=to_spdxid,
336 relationshipType=relationship,
337 )
338
339 if comment is not None:
340 r.comment = comment
341
Andrew Geisslereff27472021-10-29 15:35:00 -0500342 if annotation is not None:
343 r.annotations.append(annotation)
344
Andrew Geissler5199d832021-09-24 16:47:35 -0500345 self.relationships.append(r)
346
347 def find_by_spdxid(self, spdxid):
348 for o in itertools.chain(self.packages, self.files):
349 if o.SPDXID == spdxid:
350 return o
351 return None
352
353 def find_external_document_ref(self, namespace):
354 for r in self.externalDocumentRefs:
355 if r.spdxDocument == namespace:
356 return r
357 return None