blob: 9814fbfd663c952221b77d5d9995986e9d4f66c7 [file] [log] [blame]
Andrew Geissler5199d832021-09-24 16:47:35 -05001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5import hashlib
6import itertools
7import json
8
9SPDX_VERSION = "2.2"
10
11
12class _Property(object):
13 def __init__(self, *, default=None):
14 self.default = default
15
16 def setdefault(self, dest, name):
17 if self.default is not None:
18 dest.setdefault(name, self.default)
19
20
21class _String(_Property):
22 def __init__(self, **kwargs):
23 super().__init__(**kwargs)
24
25 def set_property(self, attrs, name):
26 def get_helper(obj):
27 return obj._spdx[name]
28
29 def set_helper(obj, value):
30 obj._spdx[name] = value
31
32 def del_helper(obj):
33 del obj._spdx[name]
34
35 attrs[name] = property(get_helper, set_helper, del_helper)
36
37 def init(self, source):
38 return source
39
40
41class _Object(_Property):
42 def __init__(self, cls, **kwargs):
43 super().__init__(**kwargs)
44 self.cls = cls
45
46 def set_property(self, attrs, name):
47 def get_helper(obj):
48 if not name in obj._spdx:
49 obj._spdx[name] = self.cls()
50 return obj._spdx[name]
51
52 def set_helper(obj, value):
53 obj._spdx[name] = value
54
55 def del_helper(obj):
56 del obj._spdx[name]
57
58 attrs[name] = property(get_helper, set_helper)
59
60 def init(self, source):
61 return self.cls(**source)
62
63
64class _ListProperty(_Property):
65 def __init__(self, prop, **kwargs):
66 super().__init__(**kwargs)
67 self.prop = prop
68
69 def set_property(self, attrs, name):
70 def get_helper(obj):
71 if not name in obj._spdx:
72 obj._spdx[name] = []
73 return obj._spdx[name]
74
75 def del_helper(obj):
76 del obj._spdx[name]
77
78 attrs[name] = property(get_helper, None, del_helper)
79
80 def init(self, source):
81 return [self.prop.init(o) for o in source]
82
83
84class _StringList(_ListProperty):
85 def __init__(self, **kwargs):
86 super().__init__(_String(), **kwargs)
87
88
89class _ObjectList(_ListProperty):
90 def __init__(self, cls, **kwargs):
91 super().__init__(_Object(cls), **kwargs)
92
93
94class MetaSPDXObject(type):
95 def __new__(mcls, name, bases, attrs):
96 attrs["_properties"] = {}
97
98 for key in attrs.keys():
99 if isinstance(attrs[key], _Property):
100 prop = attrs[key]
101 attrs["_properties"][key] = prop
102 prop.set_property(attrs, key)
103
104 return super().__new__(mcls, name, bases, attrs)
105
106
107class SPDXObject(metaclass=MetaSPDXObject):
108 def __init__(self, **d):
109 self._spdx = {}
110
111 for name, prop in self._properties.items():
112 prop.setdefault(self._spdx, name)
113 if name in d:
114 self._spdx[name] = prop.init(d[name])
115
116 def serializer(self):
117 return self._spdx
118
119 def __setattr__(self, name, value):
120 if name in self._properties or name == "_spdx":
121 super().__setattr__(name, value)
122 return
123 raise KeyError("%r is not a valid SPDX property" % name)
124
125
126class SPDXChecksum(SPDXObject):
127 algorithm = _String()
128 checksumValue = _String()
129
130
131class SPDXRelationship(SPDXObject):
132 spdxElementId = _String()
133 relatedSpdxElement = _String()
134 relationshipType = _String()
135 comment = _String()
136
137
138class SPDXExternalReference(SPDXObject):
139 referenceCategory = _String()
140 referenceType = _String()
141 referenceLocator = _String()
142
143
144class SPDXPackageVerificationCode(SPDXObject):
145 packageVerificationCodeValue = _String()
146 packageVerificationCodeExcludedFiles = _StringList()
147
148
149class SPDXPackage(SPDXObject):
150 name = _String()
151 SPDXID = _String()
152 versionInfo = _String()
153 downloadLocation = _String(default="NOASSERTION")
154 packageSupplier = _String(default="NOASSERTION")
155 homepage = _String()
156 licenseConcluded = _String(default="NOASSERTION")
157 licenseDeclared = _String(default="NOASSERTION")
158 summary = _String()
159 description = _String()
160 sourceInfo = _String()
161 copyrightText = _String(default="NOASSERTION")
162 licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
163 externalRefs = _ObjectList(SPDXExternalReference)
164 packageVerificationCode = _Object(SPDXPackageVerificationCode)
165 hasFiles = _StringList()
166 packageFileName = _String()
167
168
169class SPDXFile(SPDXObject):
170 SPDXID = _String()
171 fileName = _String()
172 licenseConcluded = _String(default="NOASSERTION")
173 copyrightText = _String(default="NOASSERTION")
174 licenseInfoInFiles = _StringList(default=["NOASSERTION"])
175 checksums = _ObjectList(SPDXChecksum)
176 fileTypes = _StringList()
177
178
179class SPDXCreationInfo(SPDXObject):
180 created = _String()
181 licenseListVersion = _String()
182 comment = _String()
183 creators = _StringList()
184
185
186class SPDXExternalDocumentRef(SPDXObject):
187 externalDocumentId = _String()
188 spdxDocument = _String()
189 checksum = _Object(SPDXChecksum)
190
191
192class SPDXExtractedLicensingInfo(SPDXObject):
193 name = _String()
194 comment = _String()
195 licenseId = _String()
196 extractedText = _String()
197
198
199class SPDXDocument(SPDXObject):
200 spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
201 dataLicense = _String(default="CC0-1.0")
202 SPDXID = _String(default="SPDXRef-DOCUMENT")
203 name = _String()
204 documentNamespace = _String()
205 creationInfo = _Object(SPDXCreationInfo)
206 packages = _ObjectList(SPDXPackage)
207 files = _ObjectList(SPDXFile)
208 relationships = _ObjectList(SPDXRelationship)
209 externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
210 hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
211
212 def __init__(self, **d):
213 super().__init__(**d)
214
215 def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
216 class Encoder(json.JSONEncoder):
217 def default(self, o):
218 if isinstance(o, SPDXObject):
219 return o.serializer()
220
221 return super().default(o)
222
223 sha1 = hashlib.sha1()
224 for chunk in Encoder(
225 sort_keys=sort_keys,
226 indent=indent,
227 separators=separators,
228 ).iterencode(self):
229 chunk = chunk.encode("utf-8")
230 f.write(chunk)
231 sha1.update(chunk)
232
233 return sha1.hexdigest()
234
235 @classmethod
236 def from_json(cls, f):
237 return cls(**json.load(f))
238
239 def add_relationship(self, _from, relationship, _to, *, comment=None):
240 if isinstance(_from, SPDXObject):
241 from_spdxid = _from.SPDXID
242 else:
243 from_spdxid = _from
244
245 if isinstance(_to, SPDXObject):
246 to_spdxid = _to.SPDXID
247 else:
248 to_spdxid = _to
249
250 r = SPDXRelationship(
251 spdxElementId=from_spdxid,
252 relatedSpdxElement=to_spdxid,
253 relationshipType=relationship,
254 )
255
256 if comment is not None:
257 r.comment = comment
258
259 self.relationships.append(r)
260
261 def find_by_spdxid(self, spdxid):
262 for o in itertools.chain(self.packages, self.files):
263 if o.SPDXID == spdxid:
264 return o
265 return None
266
267 def find_external_document_ref(self, namespace):
268 for r in self.externalDocumentRefs:
269 if r.spdxDocument == namespace:
270 return r
271 return None