xref: /openbmc/openbmc/poky/meta/lib/oe/spdx.py (revision eff27476)
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 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):
118    """
119    A list of strings as a property for an SPDX object
120    """
121
122    def __init__(self, **kwargs):
123        super().__init__(_String(), **kwargs)
124
125
126class _ObjectList(_ListProperty):
127    """
128    A list of SPDX objects as a property for an SPDX object
129    """
130
131    def __init__(self, cls, **kwargs):
132        super().__init__(_Object(cls), **kwargs)
133
134
135class MetaSPDXObject(type):
136    """
137    A metaclass that allows properties (anything derived from a _Property
138    class) to be defined for a SPDX object
139    """
140    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):
153    """
154    The base SPDX object; all SPDX spec classes must derive from this class
155    """
156    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
173#
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()
188
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    annotations = _ObjectList(SPDXAnnotation)
200
201
202class SPDXExternalReference(SPDXObject):
203    referenceCategory = _String()
204    referenceType = _String()
205    referenceLocator = _String()
206
207
208class SPDXPackageVerificationCode(SPDXObject):
209    packageVerificationCodeValue = _String()
210    packageVerificationCodeExcludedFiles = _StringList()
211
212
213class SPDXPackage(SPDXObject):
214    name = _String()
215    SPDXID = _String()
216    versionInfo = _String()
217    downloadLocation = _String(default="NOASSERTION")
218    packageSupplier = _String(default="NOASSERTION")
219    homepage = _String()
220    licenseConcluded = _String(default="NOASSERTION")
221    licenseDeclared = _String(default="NOASSERTION")
222    summary = _String()
223    description = _String()
224    sourceInfo = _String()
225    copyrightText = _String(default="NOASSERTION")
226    licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
227    externalRefs = _ObjectList(SPDXExternalReference)
228    packageVerificationCode = _Object(SPDXPackageVerificationCode)
229    hasFiles = _StringList()
230    packageFileName = _String()
231    annotations = _ObjectList(SPDXAnnotation)
232
233
234class SPDXFile(SPDXObject):
235    SPDXID = _String()
236    fileName = _String()
237    licenseConcluded = _String(default="NOASSERTION")
238    copyrightText = _String(default="NOASSERTION")
239    licenseInfoInFiles = _StringList(default=["NOASSERTION"])
240    checksums = _ObjectList(SPDXChecksum)
241    fileTypes = _StringList()
242
243
244class SPDXCreationInfo(SPDXObject):
245    created = _String()
246    licenseListVersion = _String()
247    comment = _String()
248    creators = _StringList()
249
250
251class SPDXExternalDocumentRef(SPDXObject):
252    externalDocumentId = _String()
253    spdxDocument = _String()
254    checksum = _Object(SPDXChecksum)
255
256
257class SPDXExtractedLicensingInfo(SPDXObject):
258    name = _String()
259    comment = _String()
260    licenseId = _String()
261    extractedText = _String()
262
263
264class SPDXDocument(SPDXObject):
265    spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
266    dataLicense = _String(default="CC0-1.0")
267    SPDXID = _String(default="SPDXRef-DOCUMENT")
268    name = _String()
269    documentNamespace = _String()
270    creationInfo = _Object(SPDXCreationInfo)
271    packages = _ObjectList(SPDXPackage)
272    files = _ObjectList(SPDXFile)
273    relationships = _ObjectList(SPDXRelationship)
274    externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
275    hasExtractedLicensingInfos = _ObjectList(SPDXExtractedLicensingInfo)
276
277    def __init__(self, **d):
278        super().__init__(**d)
279
280    def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
281        class Encoder(json.JSONEncoder):
282            def default(self, o):
283                if isinstance(o, SPDXObject):
284                    return o.serializer()
285
286                return super().default(o)
287
288        sha1 = hashlib.sha1()
289        for chunk in Encoder(
290            sort_keys=sort_keys,
291            indent=indent,
292            separators=separators,
293        ).iterencode(self):
294            chunk = chunk.encode("utf-8")
295            f.write(chunk)
296            sha1.update(chunk)
297
298        return sha1.hexdigest()
299
300    @classmethod
301    def from_json(cls, f):
302        return cls(**json.load(f))
303
304    def add_relationship(self, _from, relationship, _to, *, comment=None, annotation=None):
305        if isinstance(_from, SPDXObject):
306            from_spdxid = _from.SPDXID
307        else:
308            from_spdxid = _from
309
310        if isinstance(_to, SPDXObject):
311            to_spdxid = _to.SPDXID
312        else:
313            to_spdxid = _to
314
315        r = SPDXRelationship(
316            spdxElementId=from_spdxid,
317            relatedSpdxElement=to_spdxid,
318            relationshipType=relationship,
319        )
320
321        if comment is not None:
322            r.comment = comment
323
324        if annotation is not None:
325            r.annotations.append(annotation)
326
327        self.relationships.append(r)
328
329    def find_by_spdxid(self, spdxid):
330        for o in itertools.chain(self.packages, self.files):
331            if o.SPDXID == spdxid:
332                return o
333        return None
334
335    def find_external_document_ref(self, namespace):
336        for r in self.externalDocumentRefs:
337            if r.spdxDocument == namespace:
338                return r
339        return None
340