xref: /openbmc/openbmc/poky/meta/lib/oe/spdx.py (revision 2390b1b6)
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7#
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
19import hashlib
20import itertools
21import json
22
23SPDX_VERSION = "2.2"
24
25
26#
27# The following are the support classes that are used to implement SPDX object
28#
29
30class _Property(object):
31    """
32    A generic SPDX object property. The different types will derive from this
33    class
34    """
35
36    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):
45    """
46    A scalar string property for an SPDX object
47    """
48
49    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):
69    """
70    A scalar SPDX object property of a SPDX object
71    """
72
73    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):
96    """
97    A list of SPDX properties
98    """
99
100    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
110        def set_helper(obj, value):
111            obj._spdx[name] = list(value)
112
113        def del_helper(obj):
114            del obj._spdx[name]
115
116        attrs[name] = property(get_helper, set_helper, del_helper)
117
118    def init(self, source):
119        return [self.prop.init(o) for o in source]
120
121
122class _StringList(_ListProperty):
123    """
124    A list of strings as a property for an SPDX object
125    """
126
127    def __init__(self, **kwargs):
128        super().__init__(_String(), **kwargs)
129
130
131class _ObjectList(_ListProperty):
132    """
133    A list of SPDX objects as a property for an SPDX object
134    """
135
136    def __init__(self, cls, **kwargs):
137        super().__init__(_Object(cls), **kwargs)
138
139
140class MetaSPDXObject(type):
141    """
142    A metaclass that allows properties (anything derived from a _Property
143    class) to be defined for a SPDX object
144    """
145    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):
158    """
159    The base SPDX object; all SPDX spec classes must derive from this class
160    """
161    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
178#
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()
193
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()
204    annotations = _ObjectList(SPDXAnnotation)
205
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):
219    name = _String()
220    SPDXID = _String()
221    versionInfo = _String()
222    downloadLocation = _String(default="NOASSERTION")
223    supplier = _String(default="NOASSERTION")
224    homepage = _String()
225    licenseConcluded = _String(default="NOASSERTION")
226    licenseDeclared = _String(default="NOASSERTION")
227    summary = _String()
228    description = _String()
229    sourceInfo = _String()
230    copyrightText = _String(default="NOASSERTION")
231    licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
232    externalRefs = _ObjectList(SPDXExternalReference)
233    packageVerificationCode = _Object(SPDXPackageVerificationCode)
234    hasFiles = _StringList()
235    packageFileName = _String()
236    annotations = _ObjectList(SPDXAnnotation)
237
238
239class SPDXFile(SPDXObject):
240    SPDXID = _String()
241    fileName = _String()
242    licenseConcluded = _String(default="NOASSERTION")
243    copyrightText = _String(default="NOASSERTION")
244    licenseInfoInFiles = _StringList(default=["NOASSERTION"])
245    checksums = _ObjectList(SPDXChecksum)
246    fileTypes = _StringList()
247
248
249class SPDXCreationInfo(SPDXObject):
250    created = _String()
251    licenseListVersion = _String()
252    comment = _String()
253    creators = _StringList()
254
255
256class SPDXExternalDocumentRef(SPDXObject):
257    externalDocumentId = _String()
258    spdxDocument = _String()
259    checksum = _Object(SPDXChecksum)
260
261
262class SPDXExtractedLicensingInfo(SPDXObject):
263    name = _String()
264    comment = _String()
265    licenseId = _String()
266    extractedText = _String()
267
268
269class SPDXDocument(SPDXObject):
270    spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
271    dataLicense = _String(default="CC0-1.0")
272    SPDXID = _String(default="SPDXRef-DOCUMENT")
273    name = _String()
274    documentNamespace = _String()
275    creationInfo = _Object(SPDXCreationInfo)
276    packages = _ObjectList(SPDXPackage)
277    files = _ObjectList(SPDXFile)
278    relationships = _ObjectList(SPDXRelationship)
279    externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
280    hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
281
282    def __init__(self, **d):
283        super().__init__(**d)
284
285    def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
286        class Encoder(json.JSONEncoder):
287            def default(self, o):
288                if isinstance(o, SPDXObject):
289                    return o.serializer()
290
291                return super().default(o)
292
293        sha1 = hashlib.sha1()
294        for chunk in Encoder(
295            sort_keys=sort_keys,
296            indent=indent,
297            separators=separators,
298        ).iterencode(self):
299            chunk = chunk.encode("utf-8")
300            f.write(chunk)
301            sha1.update(chunk)
302
303        return sha1.hexdigest()
304
305    @classmethod
306    def from_json(cls, f):
307        return cls(**json.load(f))
308
309    def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
310        if isinstance(_from, SPDXObject):
311            from_spdxid = _from.SPDXID
312        else:
313            from_spdxid = _from
314
315        if isinstance(_to, SPDXObject):
316            to_spdxid = _to.SPDXID
317        else:
318            to_spdxid = _to
319
320        r = SPDXRelationship(
321            spdxElementId=from_spdxid,
322            relatedSpdxElement=to_spdxid,
323            relationshipType=relationship,
324        )
325
326        if comment is not None:
327            r.comment = comment
328
329        if annotation is not None:
330            r.annotations.append(annotation)
331
332        self.relationships.append(r)
333
334    def find_by_spdxid(self, spdxid):
335        for o in itertools.chain(self.packages, self.files):
336            if o.SPDXID == spdxid:
337                return o
338        return None
339
340    def find_external_document_ref(self, namespace):
341        for r in self.externalDocumentRefs:
342            if r.spdxDocument == namespace:
343                return r
344        return None
345