blob: 6d56ed90df29b3926cf9c97d105be6bfa78aef0f [file] [log] [blame]
Andrew Geissler5199d832021-09-24 16:47:35 -05001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
Patrick Williams93c203f2021-10-06 16:15:23 -05005#
6# This library is intended to capture the JSON SPDX specification in a type
7# safe manner. It is not intended to encode any particular OE specific
8# behaviors, see the sbom.py for that.
9#
10# The documented SPDX spec document doesn't cover the JSON syntax for
11# particular configuration, which can make it hard to determine what the JSON
12# syntax should be. I've found it is actually much simpler to read the official
13# SPDX JSON schema which can be found here: https://github.com/spdx/spdx-spec
14# in schemas/spdx-schema.json
15#
16
Andrew Geissler5199d832021-09-24 16:47:35 -050017import hashlib
18import itertools
19import json
20
21SPDX_VERSION = "2.2"
22
23
Patrick Williams93c203f2021-10-06 16:15:23 -050024#
25# The following are the support classes that are used to implement SPDX object
26#
27
Andrew Geissler5199d832021-09-24 16:47:35 -050028class _Property(object):
Patrick Williams93c203f2021-10-06 16:15:23 -050029 """
30 A generic SPDX object property. The different types will derive from this
31 class
32 """
33
Andrew Geissler5199d832021-09-24 16:47:35 -050034 def __init__(self, *, default=None):
35 self.default = default
36
37 def setdefault(self, dest, name):
38 if self.default is not None:
39 dest.setdefault(name, self.default)
40
41
42class _String(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050043 """
44 A scalar string property for an SPDX object
45 """
46
Andrew Geissler5199d832021-09-24 16:47:35 -050047 def __init__(self, **kwargs):
48 super().__init__(**kwargs)
49
50 def set_property(self, attrs, name):
51 def get_helper(obj):
52 return obj._spdx[name]
53
54 def set_helper(obj, value):
55 obj._spdx[name] = value
56
57 def del_helper(obj):
58 del obj._spdx[name]
59
60 attrs[name] = property(get_helper, set_helper, del_helper)
61
62 def init(self, source):
63 return source
64
65
66class _Object(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050067 """
68 A scalar SPDX object property of a SPDX object
69 """
70
Andrew Geissler5199d832021-09-24 16:47:35 -050071 def __init__(self, cls, **kwargs):
72 super().__init__(**kwargs)
73 self.cls = cls
74
75 def set_property(self, attrs, name):
76 def get_helper(obj):
77 if not name in obj._spdx:
78 obj._spdx[name] = self.cls()
79 return obj._spdx[name]
80
81 def set_helper(obj, value):
82 obj._spdx[name] = value
83
84 def del_helper(obj):
85 del obj._spdx[name]
86
87 attrs[name] = property(get_helper, set_helper)
88
89 def init(self, source):
90 return self.cls(**source)
91
92
93class _ListProperty(_Property):
Patrick Williams93c203f2021-10-06 16:15:23 -050094 """
95 A list of SPDX properties
96 """
97
Andrew Geissler5199d832021-09-24 16:47:35 -050098 def __init__(self, prop, **kwargs):
99 super().__init__(**kwargs)
100 self.prop = prop
101
102 def set_property(self, attrs, name):
103 def get_helper(obj):
104 if not name in obj._spdx:
105 obj._spdx[name] = []
106 return obj._spdx[name]
107
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000108 def set_helper(obj, value):
109 obj._spdx[name] = list(value)
110
Andrew Geissler5199d832021-09-24 16:47:35 -0500111 def del_helper(obj):
112 del obj._spdx[name]
113
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000114 attrs[name] = property(get_helper, set_helper, del_helper)
Andrew Geissler5199d832021-09-24 16:47:35 -0500115
116 def init(self, source):
117 return [self.prop.init(o) for o in source]
118
119
120class _StringList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500121 """
122 A list of strings as a property for an SPDX object
123 """
124
Andrew Geissler5199d832021-09-24 16:47:35 -0500125 def __init__(self, **kwargs):
126 super().__init__(_String(), **kwargs)
127
128
129class _ObjectList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500130 """
131 A list of SPDX objects as a property for an SPDX object
132 """
133
Andrew Geissler5199d832021-09-24 16:47:35 -0500134 def __init__(self, cls, **kwargs):
135 super().__init__(_Object(cls), **kwargs)
136
137
138class MetaSPDXObject(type):
Patrick Williams93c203f2021-10-06 16:15:23 -0500139 """
140 A metaclass that allows properties (anything derived from a _Property
141 class) to be defined for a SPDX object
142 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500143 def __new__(mcls, name, bases, attrs):
144 attrs["_properties"] = {}
145
146 for key in attrs.keys():
147 if isinstance(attrs[key], _Property):
148 prop = attrs[key]
149 attrs["_properties"][key] = prop
150 prop.set_property(attrs, key)
151
152 return super().__new__(mcls, name, bases, attrs)
153
154
155class SPDXObject(metaclass=MetaSPDXObject):
Patrick Williams93c203f2021-10-06 16:15:23 -0500156 """
157 The base SPDX object; all SPDX spec classes must derive from this class
158 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500159 def __init__(self, **d):
160 self._spdx = {}
161
162 for name, prop in self._properties.items():
163 prop.setdefault(self._spdx, name)
164 if name in d:
165 self._spdx[name] = prop.init(d[name])
166
167 def serializer(self):
168 return self._spdx
169
170 def __setattr__(self, name, value):
171 if name in self._properties or name == "_spdx":
172 super().__setattr__(name, value)
173 return
174 raise KeyError("%r is not a valid SPDX property" % name)
175
Patrick Williams93c203f2021-10-06 16:15:23 -0500176#
177# These are the SPDX objects implemented from the spec. The *only* properties
178# that can be added to these objects are ones directly specified in the SPDX
179# spec, however you may add helper functions to make operations easier.
180#
181# Defaults should *only* be specified if the SPDX spec says there is a certain
182# required value for a field (e.g. dataLicense), or if the field is mandatory
183# and has some sane "this field is unknown" (e.g. "NOASSERTION")
184#
185
186class SPDXAnnotation(SPDXObject):
187 annotationDate = _String()
188 annotationType = _String()
189 annotator = _String()
190 comment = _String()
Andrew Geissler5199d832021-09-24 16:47:35 -0500191
192class SPDXChecksum(SPDXObject):
193 algorithm = _String()
194 checksumValue = _String()
195
196
197class SPDXRelationship(SPDXObject):
198 spdxElementId = _String()
199 relatedSpdxElement = _String()
200 relationshipType = _String()
201 comment = _String()
Andrew Geisslereff27472021-10-29 15:35:00 -0500202 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500203
204
205class SPDXExternalReference(SPDXObject):
206 referenceCategory = _String()
207 referenceType = _String()
208 referenceLocator = _String()
209
210
211class SPDXPackageVerificationCode(SPDXObject):
212 packageVerificationCodeValue = _String()
213 packageVerificationCodeExcludedFiles = _StringList()
214
215
216class SPDXPackage(SPDXObject):
217 name = _String()
218 SPDXID = _String()
219 versionInfo = _String()
220 downloadLocation = _String(default="NOASSERTION")
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500221 supplier = _String(default="NOASSERTION")
Andrew Geissler5199d832021-09-24 16:47:35 -0500222 homepage = _String()
223 licenseConcluded = _String(default="NOASSERTION")
224 licenseDeclared = _String(default="NOASSERTION")
225 summary = _String()
226 description = _String()
227 sourceInfo = _String()
228 copyrightText = _String(default="NOASSERTION")
229 licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
230 externalRefs = _ObjectList(SPDXExternalReference)
231 packageVerificationCode = _Object(SPDXPackageVerificationCode)
232 hasFiles = _StringList()
233 packageFileName = _String()
Patrick Williams93c203f2021-10-06 16:15:23 -0500234 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500235
236
237class SPDXFile(SPDXObject):
238 SPDXID = _String()
239 fileName = _String()
240 licenseConcluded = _String(default="NOASSERTION")
241 copyrightText = _String(default="NOASSERTION")
242 licenseInfoInFiles = _StringList(default=["NOASSERTION"])
243 checksums = _ObjectList(SPDXChecksum)
244 fileTypes = _StringList()
245
246
247class SPDXCreationInfo(SPDXObject):
248 created = _String()
249 licenseListVersion = _String()
250 comment = _String()
251 creators = _StringList()
252
253
254class SPDXExternalDocumentRef(SPDXObject):
255 externalDocumentId = _String()
256 spdxDocument = _String()
257 checksum = _Object(SPDXChecksum)
258
259
260class SPDXExtractedLicensingInfo(SPDXObject):
261 name = _String()
262 comment = _String()
263 licenseId = _String()
264 extractedText = _String()
265
266
267class SPDXDocument(SPDXObject):
268 spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
269 dataLicense = _String(default="CC0-1.0")
270 SPDXID = _String(default="SPDXRef-DOCUMENT")
271 name = _String()
272 documentNamespace = _String()
273 creationInfo = _Object(SPDXCreationInfo)
274 packages = _ObjectList(SPDXPackage)
275 files = _ObjectList(SPDXFile)
276 relationships = _ObjectList(SPDXRelationship)
277 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
278 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
279
280 def __init__(self, **d):
281 super().__init__(**d)
282
283 def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
284 class Encoder(json.JSONEncoder):
285 def default(self, o):
286 if isinstance(o, SPDXObject):
287 return o.serializer()
288
289 return super().default(o)
290
291 sha1 = hashlib.sha1()
292 for chunk in Encoder(
293 sort_keys=sort_keys,
294 indent=indent,
295 separators=separators,
296 ).iterencode(self):
297 chunk = chunk.encode("utf-8")
298 f.write(chunk)
299 sha1.update(chunk)
300
301 return sha1.hexdigest()
302
303 @classmethod
304 def from_json(cls, f):
305 return cls(**json.load(f))
306
Andrew Geisslereff27472021-10-29 15:35:00 -0500307 def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
Andrew Geissler5199d832021-09-24 16:47:35 -0500308 if isinstance(_from, SPDXObject):
309 from_spdxid = _from.SPDXID
310 else:
311 from_spdxid = _from
312
313 if isinstance(_to, SPDXObject):
314 to_spdxid = _to.SPDXID
315 else:
316 to_spdxid = _to
317
318 r = SPDXRelationship(
319 spdxElementId=from_spdxid,
320 relatedSpdxElement=to_spdxid,
321 relationshipType=relationship,
322 )
323
324 if comment is not None:
325 r.comment = comment
326
Andrew Geisslereff27472021-10-29 15:35:00 -0500327 if annotation is not None:
328 r.annotations.append(annotation)
329
Andrew Geissler5199d832021-09-24 16:47:35 -0500330 self.relationships.append(r)
331
332 def find_by_spdxid(self, spdxid):
333 for o in itertools.chain(self.packages, self.files):
334 if o.SPDXID == spdxid:
335 return o
336 return None
337
338 def find_external_document_ref(self, namespace):
339 for r in self.externalDocumentRefs:
340 if r.spdxDocument == namespace:
341 return r
342 return None