xref: /openbmc/openbmc/poky/meta/lib/oe/spdx.py (revision edff4923)
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    ALLOWED_CHECKSUMS = [
220        "SHA1",
221        "SHA224",
222        "SHA256",
223        "SHA384",
224        "SHA512",
225        "MD2",
226        "MD4",
227        "MD5",
228        "MD6",
229    ]
230
231    name = _String()
232    SPDXID = _String()
233    versionInfo = _String()
234    downloadLocation = _String(default="NOASSERTION")
235    supplier = _String(default="NOASSERTION")
236    homepage = _String()
237    licenseConcluded = _String(default="NOASSERTION")
238    licenseDeclared = _String(default="NOASSERTION")
239    summary = _String()
240    description = _String()
241    sourceInfo = _String()
242    copyrightText = _String(default="NOASSERTION")
243    licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
244    externalRefs = _ObjectList(SPDXExternalReference)
245    packageVerificationCode = _Object(SPDXPackageVerificationCode)
246    hasFiles = _StringList()
247    packageFileName = _String()
248    annotations = _ObjectList(SPDXAnnotation)
249    checksums = _ObjectList(SPDXChecksum)
250
251
252class SPDXFile(SPDXObject):
253    SPDXID = _String()
254    fileName = _String()
255    licenseConcluded = _String(default="NOASSERTION")
256    copyrightText = _String(default="NOASSERTION")
257    licenseInfoInFiles = _StringList(default=["NOASSERTION"])
258    checksums = _ObjectList(SPDXChecksum)
259    fileTypes = _StringList()
260
261
262class SPDXCreationInfo(SPDXObject):
263    created = _String()
264    licenseListVersion = _String()
265    comment = _String()
266    creators = _StringList()
267
268
269class SPDXExternalDocumentRef(SPDXObject):
270    externalDocumentId = _String()
271    spdxDocument = _String()
272    checksum = _Object(SPDXChecksum)
273
274
275class SPDXExtractedLicensingInfo(SPDXObject):
276    name = _String()
277    comment = _String()
278    licenseId = _String()
279    extractedText = _String()
280
281
282class SPDXDocument(SPDXObject):
283    spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
284    dataLicense = _String(default="CC0-1.0")
285    SPDXID = _String(default="SPDXRef-DOCUMENT")
286    name = _String()
287    documentNamespace = _String()
288    creationInfo = _Object(SPDXCreationInfo)
289    packages = _ObjectList(SPDXPackage)
290    files = _ObjectList(SPDXFile)
291    relationships = _ObjectList(SPDXRelationship)
292    externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
293    hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
294
295    def __init__(self, **d):
296        super().__init__(**d)
297
298    def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
299        class Encoder(json.JSONEncoder):
300            def default(self, o):
301                if isinstance(o, SPDXObject):
302                    return o.serializer()
303
304                return super().default(o)
305
306        sha1 = hashlib.sha1()
307        for chunk in Encoder(
308            sort_keys=sort_keys,
309            indent=indent,
310            separators=separators,
311        ).iterencode(self):
312            chunk = chunk.encode("utf-8")
313            f.write(chunk)
314            sha1.update(chunk)
315
316        return sha1.hexdigest()
317
318    @classmethod
319    def from_json(cls, f):
320        return cls(**json.load(f))
321
322    def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
323        if isinstance(_from, SPDXObject):
324            from_spdxid = _from.SPDXID
325        else:
326            from_spdxid = _from
327
328        if isinstance(_to, SPDXObject):
329            to_spdxid = _to.SPDXID
330        else:
331            to_spdxid = _to
332
333        r = SPDXRelationship(
334            spdxElementId=from_spdxid,
335            relatedSpdxElement=to_spdxid,
336            relationshipType=relationship,
337        )
338
339        if comment is not None:
340            r.comment = comment
341
342        if annotation is not None:
343            r.annotations.append(annotation)
344
345        self.relationships.append(r)
346
347    def find_by_spdxid(self, spdxid):
348        for o in itertools.chain(self.packages, self.files):
349            if o.SPDXID == spdxid:
350                return o
351        return None
352
353    def find_external_document_ref(self, namespace):
354        for r in self.externalDocumentRefs:
355            if r.spdxDocument == namespace:
356                return r
357        return None
358