xref: /openbmc/openbmc/poky/meta/lib/oe/spdx.py (revision db4c27ee)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5#
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
17import hashlib
18import itertools
19import json
20
21SPDX_VERSION = "2.2"
22
23
24#
25# The following are the support classes that are used to implement SPDX object
26#
27
28class _Property(object):
29    """
30    A generic SPDX object property. The different types will derive from this
31    class
32    """
33
34    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):
43    """
44    A scalar string property for an SPDX object
45    """
46
47    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):
67    """
68    A scalar SPDX object property of a SPDX object
69    """
70
71    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):
94    """
95    A list of SPDX properties
96    """
97
98    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 set_helper(obj, value):
109            obj._spdx[name] = list(value)
110
111        def del_helper(obj):
112            del obj._spdx[name]
113
114        attrs[name] = property(get_helper, set_helper, del_helper)
115
116    def init(self, source):
117        return [self.prop.init(o) for o in source]
118
119
120class _StringList(_ListProperty):
121    """
122    A list of strings as a property for an SPDX object
123    """
124
125    def __init__(self, **kwargs):
126        super().__init__(_String(), **kwargs)
127
128
129class _ObjectList(_ListProperty):
130    """
131    A list of SPDX objects as a property for an SPDX object
132    """
133
134    def __init__(self, cls, **kwargs):
135        super().__init__(_Object(cls), **kwargs)
136
137
138class MetaSPDXObject(type):
139    """
140    A metaclass that allows properties (anything derived from a _Property
141    class) to be defined for a SPDX object
142    """
143    def __new__(mcls, name, bases, attrs):
144        attrs["_properties"] = {}
145
146        for key in attrs.keys():
147            if isinstance(attrs[key], _Property):
148                prop = attrs[key]
149                attrs["_properties"][key] = prop
150                prop.set_property(attrs, key)
151
152        return super().__new__(mcls, name, bases, attrs)
153
154
155class SPDXObject(metaclass=MetaSPDXObject):
156    """
157    The base SPDX object; all SPDX spec classes must derive from this class
158    """
159    def __init__(self, **d):
160        self._spdx = {}
161
162        for name, prop in self._properties.items():
163            prop.setdefault(self._spdx, name)
164            if name in d:
165                self._spdx[name] = prop.init(d[name])
166
167    def serializer(self):
168        return self._spdx
169
170    def __setattr__(self, name, value):
171        if name in self._properties or name == "_spdx":
172            super().__setattr__(name, value)
173            return
174        raise KeyError("%r is not a valid SPDX property" % name)
175
176#
177# These are the SPDX objects implemented from the spec. The *only* properties
178# that can be added to these objects are ones directly specified in the SPDX
179# spec, however you may add helper functions to make operations easier.
180#
181# Defaults should *only* be specified if the SPDX spec says there is a certain
182# required value for a field (e.g. dataLicense), or if the field is mandatory
183# and has some sane "this field is unknown" (e.g. "NOASSERTION")
184#
185
186class SPDXAnnotation(SPDXObject):
187    annotationDate = _String()
188    annotationType = _String()
189    annotator = _String()
190    comment = _String()
191
192class SPDXChecksum(SPDXObject):
193    algorithm = _String()
194    checksumValue = _String()
195
196
197class SPDXRelationship(SPDXObject):
198    spdxElementId = _String()
199    relatedSpdxElement = _String()
200    relationshipType = _String()
201    comment = _String()
202    annotations = _ObjectList(SPDXAnnotation)
203
204
205class SPDXExternalReference(SPDXObject):
206    referenceCategory = _String()
207    referenceType = _String()
208    referenceLocator = _String()
209
210
211class SPDXPackageVerificationCode(SPDXObject):
212    packageVerificationCodeValue = _String()
213    packageVerificationCodeExcludedFiles = _StringList()
214
215
216class SPDXPackage(SPDXObject):
217    name = _String()
218    SPDXID = _String()
219    versionInfo = _String()
220    downloadLocation = _String(default="NOASSERTION")
221    supplier = _String(default="NOASSERTION")
222    homepage = _String()
223    licenseConcluded = _String(default="NOASSERTION")
224    licenseDeclared = _String(default="NOASSERTION")
225    summary = _String()
226    description = _String()
227    sourceInfo = _String()
228    copyrightText = _String(default="NOASSERTION")
229    licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
230    externalRefs = _ObjectList(SPDXExternalReference)
231    packageVerificationCode = _Object(SPDXPackageVerificationCode)
232    hasFiles = _StringList()
233    packageFileName = _String()
234    annotations = _ObjectList(SPDXAnnotation)
235
236
237class SPDXFile(SPDXObject):
238    SPDXID = _String()
239    fileName = _String()
240    licenseConcluded = _String(default="NOASSERTION")
241    copyrightText = _String(default="NOASSERTION")
242    licenseInfoInFiles = _StringList(default=["NOASSERTION"])
243    checksums = _ObjectList(SPDXChecksum)
244    fileTypes = _StringList()
245
246
247class SPDXCreationInfo(SPDXObject):
248    created = _String()
249    licenseListVersion = _String()
250    comment = _String()
251    creators = _StringList()
252
253
254class SPDXExternalDocumentRef(SPDXObject):
255    externalDocumentId = _String()
256    spdxDocument = _String()
257    checksum = _Object(SPDXChecksum)
258
259
260class SPDXExtractedLicensingInfo(SPDXObject):
261    name = _String()
262    comment = _String()
263    licenseId = _String()
264    extractedText = _String()
265
266
267class SPDXDocument(SPDXObject):
268    spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
269    dataLicense = _String(default="CC0-1.0")
270    SPDXID = _String(default="SPDXRef-DOCUMENT")
271    name = _String()
272    documentNamespace = _String()
273    creationInfo = _Object(SPDXCreationInfo)
274    packages = _ObjectList(SPDXPackage)
275    files = _ObjectList(SPDXFile)
276    relationships = _ObjectList(SPDXRelationship)
277    externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
278    hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
279
280    def __init__(self, **d):
281        super().__init__(**d)
282
283    def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
284        class Encoder(json.JSONEncoder):
285            def default(self, o):
286                if isinstance(o, SPDXObject):
287                    return o.serializer()
288
289                return super().default(o)
290
291        sha1 = hashlib.sha1()
292        for chunk in Encoder(
293            sort_keys=sort_keys,
294            indent=indent,
295            separators=separators,
296        ).iterencode(self):
297            chunk = chunk.encode("utf-8")
298            f.write(chunk)
299            sha1.update(chunk)
300
301        return sha1.hexdigest()
302
303    @classmethod
304    def from_json(cls, f):
305        return cls(**json.load(f))
306
307    def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
308        if isinstance(_from, SPDXObject):
309            from_spdxid = _from.SPDXID
310        else:
311            from_spdxid = _from
312
313        if isinstance(_to, SPDXObject):
314            to_spdxid = _to.SPDXID
315        else:
316            to_spdxid = _to
317
318        r = SPDXRelationship(
319            spdxElementId=from_spdxid,
320            relatedSpdxElement=to_spdxid,
321            relationshipType=relationship,
322        )
323
324        if comment is not None:
325            r.comment = comment
326
327        if annotation is not None:
328            r.annotations.append(annotation)
329
330        self.relationships.append(r)
331
332    def find_by_spdxid(self, spdxid):
333        for o in itertools.chain(self.packages, self.files):
334            if o.SPDXID == spdxid:
335                return o
336        return None
337
338    def find_external_document_ref(self, namespace):
339        for r in self.externalDocumentRefs:
340            if r.spdxDocument == namespace:
341                return r
342        return None
343