blob: 4416194e0622998b8ea8fca8a1d231a620cb5441 [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()
199
200
201class SPDXExternalReference(SPDXObject):
202 referenceCategory = _String()
203 referenceType = _String()
204 referenceLocator = _String()
205
206
207class SPDXPackageVerificationCode(SPDXObject):
208 packageVerificationCodeValue = _String()
209 packageVerificationCodeExcludedFiles = _StringList()
210
211
212class SPDXPackage(SPDXObject):
213 name = _String()
214 SPDXID = _String()
215 versionInfo = _String()
216 downloadLocation = _String(default="NOASSERTION")
217 packageSupplier = _String(default="NOASSERTION")
218 homepage = _String()
219 licenseConcluded = _String(default="NOASSERTION")
220 licenseDeclared = _String(default="NOASSERTION")
221 summary = _String()
222 description = _String()
223 sourceInfo = _String()
224 copyrightText = _String(default="NOASSERTION")
225 licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
226 externalRefs = _ObjectList(SPDXExternalReference)
227 packageVerificationCode = _Object(SPDXPackageVerificationCode)
228 hasFiles = _StringList()
229 packageFileName = _String()
Patrick Williams93c203f2021-10-06 16:15:23 -0500230 annotations = _ObjectList(SPDXAnnotation)
Andrew Geissler5199d832021-09-24 16:47:35 -0500231
232
233class SPDXFile(SPDXObject):
234 SPDXID = _String()
235 fileName = _String()
236 licenseConcluded = _String(default="NOASSERTION")
237 copyrightText = _String(default="NOASSERTION")
238 licenseInfoInFiles = _StringList(default=["NOASSERTION"])
239 checksums = _ObjectList(SPDXChecksum)
240 fileTypes = _StringList()
241
242
243class SPDXCreationInfo(SPDXObject):
244 created = _String()
245 licenseListVersion = _String()
246 comment = _String()
247 creators = _StringList()
248
249
250class SPDXExternalDocumentRef(SPDXObject):
251 externalDocumentId = _String()
252 spdxDocument = _String()
253 checksum = _Object(SPDXChecksum)
254
255
256class SPDXExtractedLicensingInfo(SPDXObject):
257 name = _String()
258 comment = _String()
259 licenseId = _String()
260 extractedText = _String()
261
262
263class SPDXDocument(SPDXObject):
264 spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
265 dataLicense = _String(default="CC0-1.0")
266 SPDXID = _String(default="SPDXRef-DOCUMENT")
267 name = _String()
268 documentNamespace = _String()
269 creationInfo = _Object(SPDXCreationInfo)
270 packages = _ObjectList(SPDXPackage)
271 files = _ObjectList(SPDXFile)
272 relationships = _ObjectList(SPDXRelationship)
273 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
274 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
275
276 def __init__(self, **d):
277 super().__init__(**d)
278
279 def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
280 class Encoder(json.JSONEncoder):
281 def default(self, o):
282 if isinstance(o, SPDXObject):
283 return o.serializer()
284
285 return super().default(o)
286
287 sha1 = hashlib.sha1()
288 for chunk in Encoder(
289 sort_keys=sort_keys,
290 indent=indent,
291 separators=separators,
292 ).iterencode(self):
293 chunk = chunk.encode("utf-8")
294 f.write(chunk)
295 sha1.update(chunk)
296
297 return sha1.hexdigest()
298
299 @classmethod
300 def from_json(cls, f):
301 return cls(**json.load(f))
302
303 def add_relationship(self, _from, relationship, _to, *, comment=None):
304 if isinstance(_from, SPDXObject):
305 from_spdxid = _from.SPDXID
306 else:
307 from_spdxid = _from
308
309 if isinstance(_to, SPDXObject):
310 to_spdxid = _to.SPDXID
311 else:
312 to_spdxid = _to
313
314 r = SPDXRelationship(
315 spdxElementId=from_spdxid,
316 relatedSpdxElement=to_spdxid,
317 relationshipType=relationship,
318 )
319
320 if comment is not None:
321 r.comment = comment
322
323 self.relationships.append(r)
324
325 def find_by_spdxid(self, spdxid):
326 for o in itertools.chain(self.packages, self.files):
327 if o.SPDXID == spdxid:
328 return o
329 return None
330
331 def find_external_document_ref(self, namespace):
332 for r in self.externalDocumentRefs:
333 if r.spdxDocument == namespace:
334 return r
335 return None