blob: 9e7ced5a155cd0954ea3fc90eb88450f37b7a618 [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
108 def del_helper(obj):
109 del obj._spdx[name]
110
111 attrs[name] = property(get_helper, None, del_helper)
112
113 def init(self, source):
114 return [self.prop.init(o) for o in source]
115
116
117class _StringList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500118 """
119 A list of strings as a property for an SPDX object
120 """
121
Andrew Geissler5199d832021-09-24 16:47:35 -0500122 def __init__(self, **kwargs):
123 super().__init__(_String(), **kwargs)
124
125
126class _ObjectList(_ListProperty):
Patrick Williams93c203f2021-10-06 16:15:23 -0500127 """
128 A list of SPDX objects as a property for an SPDX object
129 """
130
Andrew Geissler5199d832021-09-24 16:47:35 -0500131 def __init__(self, cls, **kwargs):
132 super().__init__(_Object(cls), **kwargs)
133
134
135class MetaSPDXObject(type):
Patrick Williams93c203f2021-10-06 16:15:23 -0500136 """
137 A metaclass that allows properties (anything derived from a _Property
138 class) to be defined for a SPDX object
139 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500140 def __new__(mcls, name, bases, attrs):
141 attrs["_properties"] = {}
142
143 for key in attrs.keys():
144 if isinstance(attrs[key], _Property):
145 prop = attrs[key]
146 attrs["_properties"][key] = prop
147 prop.set_property(attrs, key)
148
149 return super().__new__(mcls, name, bases, attrs)
150
151
152class SPDXObject(metaclass=MetaSPDXObject):
Patrick Williams93c203f2021-10-06 16:15:23 -0500153 """
154 The base SPDX object; all SPDX spec classes must derive from this class
155 """
Andrew Geissler5199d832021-09-24 16:47:35 -0500156 def __init__(self, **d):
157 self._spdx = {}
158
159 for name, prop in self._properties.items():
160 prop.setdefault(self._spdx, name)
161 if name in d:
162 self._spdx[name] = prop.init(d[name])
163
164 def serializer(self):
165 return self._spdx
166
167 def __setattr__(self, name, value):
168 if name in self._properties or name == "_spdx":
169 super().__setattr__(name, value)
170 return
171 raise KeyError("%r is not a valid SPDX property" % name)
172
Patrick Williams93c203f2021-10-06 16:15:23 -0500173#
174# These are the SPDX objects implemented from the spec. The *only* properties
175# that can be added to these objects are ones directly specified in the SPDX
176# spec, however you may add helper functions to make operations easier.
177#
178# Defaults should *only* be specified if the SPDX spec says there is a certain
179# required value for a field (e.g. dataLicense), or if the field is mandatory
180# and has some sane "this field is unknown" (e.g. "NOASSERTION")
181#
182
183class SPDXAnnotation(SPDXObject):
184 annotationDate = _String()
185 annotationType = _String()
186 annotator = _String()
187 comment = _String()
Andrew Geissler5199d832021-09-24 16:47:35 -0500188
189class SPDXChecksum(SPDXObject):
190 algorithm = _String()
191 checksumValue = _String()
192
193
194class SPDXRelationship(SPDXObject):
195 spdxElementId = _String()
196 relatedSpdxElement = _String()
197 relationshipType = _String()
198 comment = _String()
Andrew Geisslereff27472021-10-29 15:35:00 -0500199 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500200
201
202class SPDXExternalReference(SPDXObject):
203 referenceCategory = _String()
204 referenceType = _String()
205 referenceLocator = _String()
206
207
208class SPDXPackageVerificationCode(SPDXObject):
209 packageVerificationCodeValue = _String()
210 packageVerificationCodeExcludedFiles = _StringList()
211
212
213class SPDXPackage(SPDXObject):
214 name = _String()
215 SPDXID = _String()
216 versionInfo = _String()
217 downloadLocation = _String(default="NOASSERTION")
218 packageSupplier = _String(default="NOASSERTION")
219 homepage = _String()
220 licenseConcluded = _String(default="NOASSERTION")
221 licenseDeclared = _String(default="NOASSERTION")
222 summary = _String()
223 description = _String()
224 sourceInfo = _String()
225 copyrightText = _String(default="NOASSERTION")
226 licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
227 externalRefs = _ObjectList(SPDXExternalReference)
228 packageVerificationCode = _Object(SPDXPackageVerificationCode)
229 hasFiles = _StringList()
230 packageFileName = _String()
Patrick Williams93c203f2021-10-06 16:15:23 -0500231 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500232
233
234class SPDXFile(SPDXObject):
235 SPDXID = _String()
236 fileName = _String()
237 licenseConcluded = _String(default="NOASSERTION")
238 copyrightText = _String(default="NOASSERTION")
239 licenseInfoInFiles = _StringList(default=["NOASSERTION"])
240 checksums = _ObjectList(SPDXChecksum)
241 fileTypes = _StringList()
242
243
244class SPDXCreationInfo(SPDXObject):
245 created = _String()
246 licenseListVersion = _String()
247 comment = _String()
248 creators = _StringList()
249
250
251class SPDXExternalDocumentRef(SPDXObject):
252 externalDocumentId = _String()
253 spdxDocument = _String()
254 checksum = _Object(SPDXChecksum)
255
256
257class SPDXExtractedLicensingInfo(SPDXObject):
258 name = _String()
259 comment = _String()
260 licenseId = _String()
261 extractedText = _String()
262
263
264class SPDXDocument(SPDXObject):
265 spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
266 dataLicense = _String(default="CC0-1.0")
267 SPDXID = _String(default="SPDXRef-DOCUMENT")
268 name = _String()
269 documentNamespace = _String()
270 creationInfo = _Object(SPDXCreationInfo)
271 packages = _ObjectList(SPDXPackage)
272 files = _ObjectList(SPDXFile)
273 relationships = _ObjectList(SPDXRelationship)
274 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
275 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
276
277 def __init__(self, **d):
278 super().__init__(**d)
279
280 def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
281 class Encoder(json.JSONEncoder):
282 def default(self, o):
283 if isinstance(o, SPDXObject):
284 return o.serializer()
285
286 return super().default(o)
287
288 sha1 = hashlib.sha1()
289 for chunk in Encoder(
290 sort_keys=sort_keys,
291 indent=indent,
292 separators=separators,
293 ).iterencode(self):
294 chunk = chunk.encode("utf-8")
295 f.write(chunk)
296 sha1.update(chunk)
297
298 return sha1.hexdigest()
299
300 @classmethod
301 def from_json(cls, f):
302 return cls(**json.load(f))
303
Andrew Geisslereff27472021-10-29 15:35:00 -0500304 def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
Andrew Geissler5199d832021-09-24 16:47:35 -0500305 if isinstance(_from, SPDXObject):
306 from_spdxid = _from.SPDXID
307 else:
308 from_spdxid = _from
309
310 if isinstance(_to, SPDXObject):
311 to_spdxid = _to.SPDXID
312 else:
313 to_spdxid = _to
314
315 r = SPDXRelationship(
316 spdxElementId=from_spdxid,
317 relatedSpdxElement=to_spdxid,
318 relationshipType=relationship,
319 )
320
321 if comment is not None:
322 r.comment = comment
323
Andrew Geisslereff27472021-10-29 15:35:00 -0500324 if annotation is not None:
325 r.annotations.append(annotation)
326
Andrew Geissler5199d832021-09-24 16:47:35 -0500327 self.relationships.append(r)
328
329 def find_by_spdxid(self, spdxid):
330 for o in itertools.chain(self.packages, self.files):
331 if o.SPDXID == spdxid:
332 return o
333 return None
334
335 def find_external_document_ref(self, namespace):
336 for r in self.externalDocumentRefs:
337 if r.spdxDocument == namespace:
338 return r
339 return None