mirror of
https://git.yoctoproject.org/poky
synced 2026-06-01 00:59:48 +00:00
classes/create-spdx: Add class
Adds a class as a first attempt to create SPDX SBoM documents during the build. This initial work was influenced by [meta-doubleopen][1], although almost completely rewritten. [1]: https://github.com/doubleopen-project/meta-doubleopen (From OE-Core rev: 78c79821ae7e2f060b9a74ea9aefce98102bb00e) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
committed by
Richard Purdie
parent
7ec54b1743
commit
f1a34a63e4
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
import collections
|
||||
|
||||
DepRecipe = collections.namedtuple("DepRecipe", ("doc", "doc_sha1", "recipe"))
|
||||
DepSource = collections.namedtuple("DepSource", ("doc", "doc_sha1", "recipe", "file"))
|
||||
|
||||
|
||||
def get_recipe_spdxid(d):
|
||||
return "SPDXRef-%s-%s" % ("Recipe", d.getVar("PN"))
|
||||
|
||||
|
||||
def get_package_spdxid(pkg):
|
||||
return "SPDXRef-Package-%s" % pkg
|
||||
|
||||
|
||||
def get_source_file_spdxid(d, idx):
|
||||
return "SPDXRef-SourceFile-%s-%d" % (d.getVar("PN"), idx)
|
||||
|
||||
|
||||
def get_packaged_file_spdxid(pkg, idx):
|
||||
return "SPDXRef-PackagedFile-%s-%d" % (pkg, idx)
|
||||
|
||||
|
||||
def get_image_spdxid(img):
|
||||
return "SPDXRef-Image-%s" % img
|
||||
|
||||
|
||||
def write_doc(d, spdx_doc, subdir):
|
||||
from pathlib import Path
|
||||
|
||||
spdx_deploy = Path(d.getVar("SPDXDEPLOY"))
|
||||
|
||||
dest = spdx_deploy / subdir / (spdx_doc.name + ".spdx.json")
|
||||
dest.parent.mkdir(exist_ok=True, parents=True)
|
||||
with dest.open("wb") as f:
|
||||
doc_sha1 = spdx_doc.to_json(f, sort_keys=True)
|
||||
|
||||
l = spdx_deploy / "by-namespace" / spdx_doc.documentNamespace.replace("/", "_")
|
||||
l.parent.mkdir(exist_ok=True, parents=True)
|
||||
l.symlink_to(os.path.relpath(dest, l.parent))
|
||||
|
||||
return doc_sha1
|
||||
|
||||
|
||||
def read_doc(filename):
|
||||
import hashlib
|
||||
import oe.spdx
|
||||
|
||||
with filename.open("rb") as f:
|
||||
sha1 = hashlib.sha1()
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
sha1.update(chunk)
|
||||
|
||||
f.seek(0)
|
||||
doc = oe.spdx.SPDXDocument.from_json(f)
|
||||
|
||||
return (doc, sha1.hexdigest())
|
||||
@@ -0,0 +1,263 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
|
||||
SPDX_VERSION = "2.2"
|
||||
|
||||
|
||||
class _Property(object):
|
||||
def __init__(self, *, default=None):
|
||||
self.default = default
|
||||
|
||||
def setdefault(self, dest, name):
|
||||
if self.default is not None:
|
||||
dest.setdefault(name, self.default)
|
||||
|
||||
|
||||
class _String(_Property):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def set_property(self, attrs, name):
|
||||
def get_helper(obj):
|
||||
return obj._spdx[name]
|
||||
|
||||
def set_helper(obj, value):
|
||||
obj._spdx[name] = value
|
||||
|
||||
def del_helper(obj):
|
||||
del obj._spdx[name]
|
||||
|
||||
attrs[name] = property(get_helper, set_helper, del_helper)
|
||||
|
||||
def init(self, source):
|
||||
return source
|
||||
|
||||
|
||||
class _Object(_Property):
|
||||
def __init__(self, cls, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.cls = cls
|
||||
|
||||
def set_property(self, attrs, name):
|
||||
def get_helper(obj):
|
||||
if not name in obj._spdx:
|
||||
obj._spdx[name] = self.cls()
|
||||
return obj._spdx[name]
|
||||
|
||||
def set_helper(obj, value):
|
||||
obj._spdx[name] = value
|
||||
|
||||
def del_helper(obj):
|
||||
del obj._spdx[name]
|
||||
|
||||
attrs[name] = property(get_helper, set_helper)
|
||||
|
||||
def init(self, source):
|
||||
return self.cls(**source)
|
||||
|
||||
|
||||
class _ListProperty(_Property):
|
||||
def __init__(self, prop, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.prop = prop
|
||||
|
||||
def set_property(self, attrs, name):
|
||||
def get_helper(obj):
|
||||
if not name in obj._spdx:
|
||||
obj._spdx[name] = []
|
||||
return obj._spdx[name]
|
||||
|
||||
def del_helper(obj):
|
||||
del obj._spdx[name]
|
||||
|
||||
attrs[name] = property(get_helper, None, del_helper)
|
||||
|
||||
def init(self, source):
|
||||
return [self.prop.init(o) for o in source]
|
||||
|
||||
|
||||
class _StringList(_ListProperty):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(_String(), **kwargs)
|
||||
|
||||
|
||||
class _ObjectList(_ListProperty):
|
||||
def __init__(self, cls, **kwargs):
|
||||
super().__init__(_Object(cls), **kwargs)
|
||||
|
||||
|
||||
class MetaSPDXObject(type):
|
||||
def __new__(mcls, name, bases, attrs):
|
||||
attrs["_properties"] = {}
|
||||
|
||||
for key in attrs.keys():
|
||||
if isinstance(attrs[key], _Property):
|
||||
prop = attrs[key]
|
||||
attrs["_properties"][key] = prop
|
||||
prop.set_property(attrs, key)
|
||||
|
||||
return super().__new__(mcls, name, bases, attrs)
|
||||
|
||||
|
||||
class SPDXObject(metaclass=MetaSPDXObject):
|
||||
def __init__(self, **d):
|
||||
self._spdx = {}
|
||||
|
||||
for name, prop in self._properties.items():
|
||||
prop.setdefault(self._spdx, name)
|
||||
if name in d:
|
||||
self._spdx[name] = prop.init(d[name])
|
||||
|
||||
def serializer(self):
|
||||
return self._spdx
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self._properties or name == "_spdx":
|
||||
super().__setattr__(name, value)
|
||||
return
|
||||
raise KeyError("%r is not a valid SPDX property" % name)
|
||||
|
||||
|
||||
class SPDXChecksum(SPDXObject):
|
||||
algorithm = _String()
|
||||
checksumValue = _String()
|
||||
|
||||
|
||||
class SPDXRelationship(SPDXObject):
|
||||
spdxElementId = _String()
|
||||
relatedSpdxElement = _String()
|
||||
relationshipType = _String()
|
||||
comment = _String()
|
||||
|
||||
|
||||
class SPDXExternalReference(SPDXObject):
|
||||
referenceCategory = _String()
|
||||
referenceType = _String()
|
||||
referenceLocator = _String()
|
||||
|
||||
|
||||
class SPDXPackageVerificationCode(SPDXObject):
|
||||
packageVerificationCodeValue = _String()
|
||||
packageVerificationCodeExcludedFiles = _StringList()
|
||||
|
||||
|
||||
class SPDXPackage(SPDXObject):
|
||||
name = _String()
|
||||
SPDXID = _String()
|
||||
versionInfo = _String()
|
||||
downloadLocation = _String(default="NOASSERTION")
|
||||
packageSupplier = _String(default="NOASSERTION")
|
||||
homepage = _String()
|
||||
licenseConcluded = _String(default="NOASSERTION")
|
||||
licenseDeclared = _String(default="NOASSERTION")
|
||||
summary = _String()
|
||||
description = _String()
|
||||
sourceInfo = _String()
|
||||
copyrightText = _String(default="NOASSERTION")
|
||||
licenseInfoFromFiles = _StringList(default=["NOASSERTION"])
|
||||
externalRefs = _ObjectList(SPDXExternalReference)
|
||||
packageVerificationCode = _Object(SPDXPackageVerificationCode)
|
||||
hasFiles = _StringList()
|
||||
packageFileName = _String()
|
||||
|
||||
|
||||
class SPDXFile(SPDXObject):
|
||||
SPDXID = _String()
|
||||
fileName = _String()
|
||||
licenseConcluded = _String(default="NOASSERTION")
|
||||
copyrightText = _String(default="NOASSERTION")
|
||||
licenseInfoInFiles = _StringList(default=["NOASSERTION"])
|
||||
checksums = _ObjectList(SPDXChecksum)
|
||||
fileTypes = _StringList()
|
||||
|
||||
|
||||
class SPDXCreationInfo(SPDXObject):
|
||||
created = _String()
|
||||
licenseListVersion = _String()
|
||||
comment = _String()
|
||||
creators = _StringList()
|
||||
|
||||
|
||||
class SPDXExternalDocumentRef(SPDXObject):
|
||||
externalDocumentId = _String()
|
||||
spdxDocument = _String()
|
||||
checksum = _Object(SPDXChecksum)
|
||||
|
||||
|
||||
class SPDXDocument(SPDXObject):
|
||||
spdxVersion = _String(default="SPDX-" + SPDX_VERSION)
|
||||
dataLicense = _String(default="CC0-1.0")
|
||||
SPDXID = _String(default="SPDXRef-DOCUMENT")
|
||||
name = _String()
|
||||
documentNamespace = _String()
|
||||
creationInfo = _Object(SPDXCreationInfo)
|
||||
packages = _ObjectList(SPDXPackage)
|
||||
files = _ObjectList(SPDXFile)
|
||||
relationships = _ObjectList(SPDXRelationship)
|
||||
externalDocumentRefs = _ObjectList(SPDXExternalDocumentRef)
|
||||
|
||||
def __init__(self, **d):
|
||||
super().__init__(**d)
|
||||
|
||||
def to_json(self, f, *, sort_keys=False, indent=None, separators=None):
|
||||
class Encoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, SPDXObject):
|
||||
return o.serializer()
|
||||
|
||||
return super().default(o)
|
||||
|
||||
sha1 = hashlib.sha1()
|
||||
for chunk in Encoder(
|
||||
sort_keys=sort_keys,
|
||||
indent=indent,
|
||||
separators=separators,
|
||||
).iterencode(self):
|
||||
chunk = chunk.encode("utf-8")
|
||||
f.write(chunk)
|
||||
sha1.update(chunk)
|
||||
|
||||
return sha1.hexdigest()
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, f):
|
||||
return cls(**json.load(f))
|
||||
|
||||
def add_relationship(self, _from, relationship, _to, *, comment=None):
|
||||
if isinstance(_from, SPDXObject):
|
||||
from_spdxid = _from.SPDXID
|
||||
else:
|
||||
from_spdxid = _from
|
||||
|
||||
if isinstance(_to, SPDXObject):
|
||||
to_spdxid = _to.SPDXID
|
||||
else:
|
||||
to_spdxid = _to
|
||||
|
||||
r = SPDXRelationship(
|
||||
spdxElementId=from_spdxid,
|
||||
relatedSpdxElement=to_spdxid,
|
||||
relationshipType=relationship,
|
||||
)
|
||||
|
||||
if comment is not None:
|
||||
r.comment = comment
|
||||
|
||||
self.relationships.append(r)
|
||||
|
||||
def find_by_spdxid(self, spdxid):
|
||||
for o in itertools.chain(self.packages, self.files):
|
||||
if o.SPDXID == spdxid:
|
||||
return o
|
||||
return None
|
||||
|
||||
def find_external_document_ref(self, namespace):
|
||||
for r in self.externalDocumentRefs:
|
||||
if r.spdxDocument == namespace:
|
||||
return r
|
||||
return None
|
||||
Reference in New Issue
Block a user