cmSbom: Add SPDX object model and serializers

Adds definitions for representing software bill of materials (SBOM) information
and generating SPDX SBOMs.

Issue: #26719
Issue: #27001
This commit is contained in:
Taylor Sasser
2025-10-16 13:40:45 -04:00
committed by Daniel Tierney
parent 985b0f5bcf
commit 0653620033
13 changed files with 1480 additions and 3090 deletions

View File

@@ -701,6 +701,8 @@ add_library(
cmRemoveDefinitionsCommand.h
cmReturnCommand.cxx
cmReturnCommand.h
cmSbomObject.h
cmSbomSerializer.h
cmSearchPath.cxx
cmSearchPath.h
cmSeparateArgumentsCommand.cxx
@@ -721,9 +723,10 @@ add_library(
cmSiteNameCommand.h
cmSourceGroupCommand.cxx
cmSourceGroupCommand.h
cmSPDXSerializer.cxx
cmSPDXSerializer.h
cmSPDXTypes.def
cmSpdx.cxx
cmSpdx.h
cmSpdxSerializer.cxx
cmSpdxSerializer.h
cmString.cxx
cmString.hxx
cmStringReplaceHelper.cxx

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +0,0 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#ifndef X_SPDX
# define X_SPDX(classtype, enumid, member, camel)
#endif
// Convenience
X_SPDX(cmSPDXIdentifierReference, CM_IDENTIFIER_REFERENCE, IdentifierReference,
identifierReference)
// Core
X_SPDX(cmSPDXAgent, CORE_AGENT, Agent, agent)
X_SPDX(cmSPDXAnnotation, CORE_ANNOTATION, Annotation, annotation)
X_SPDX(cmSPDXBom, CORE_BOM, Bom, bom)
X_SPDX(cmSPDXBundle, CORE_BUNDLE, Bundle, bundle)
X_SPDX(cmSPDXCreationInfo, CORE_CREATION_INFO, CreationInfo, creationInfo)
X_SPDX(cmSPDXDictionaryEntry, CORE_DICTIONARY_ENTRY, DictionaryEntry,
dictionaryEntry)
X_SPDX(cmSPDXExternalIdentifier, CORE_EXTERNAL_IDENTIFIER, ExternalIdentifier,
externalIdentifier)
X_SPDX(cmSPDXExternalMap, CORE_EXTERNAL_MAP, ExternalMap, externalMap)
X_SPDX(cmSPDXExternalRef, CORE_EXTERNAL_REF, ExternalRef, externalRef)
X_SPDX(cmSPDXHash, CORE_HASH, Hash, hash)
X_SPDX(cmSPDXIndividualElement, CORE_INDIVIDUAL_ELEMENT, IndividualElement,
individualElement)
X_SPDX(cmSPDXLifecycleScopedRelationship, CORE_LIFECYCLE_SCOPED_RELATIONSHIP,
LifecycleScopedRelationship, lifecycleScopedRelationship)
X_SPDX(cmSPDXNamespaceMap, CORE_NAMESPACE_MAP, NamespaceMap, namespaceMap)
X_SPDX(cmSPDXOrganization, CORE_ORGANIZATION, Organization, organization)
X_SPDX(cmSPDXPackageVerificationCode, CORE_PACKAGE_VERIFICATION_CODE,
PackageVerificationCode, packageVerificationCode)
X_SPDX(cmSPDXPerson, CORE_PERSON, Person, person)
X_SPDX(cmSPDXPositiveIntegerRange, CORE_POSITIVE_INTEGER_RANGE,
PositiveIntegerRange, positiveIntegerRange)
X_SPDX(cmSPDXRelationship, CORE_RELATIONSHIP, Relationship, relationship)
X_SPDX(cmSPDXSoftwareAgent, CORE_SOFTWARE_AGENT, SoftwareAgent, softwareAgent)
X_SPDX(cmSPDXSpdxDocument, CORE_SPDX_DOCUMENT, SpdxDocument, spdxDocument)
X_SPDX(cmSPDXTool, CORE_TOOL, Tool, tool)
// Software
X_SPDX(cmSPDXContentIdentifier, SOFTWARE_CONTENT_IDENTIFIER, ContentIdentifier,
contentIdentifier)
X_SPDX(cmSPDXFile, SOFTWARE_FILE, File, file)
X_SPDX(cmSPDXPackage, SOFTWARE_PACKAGE, Package, package)
X_SPDX(cmSPDXSbom, SOFTWARE_SBOM, Sbom, sbom)
X_SPDX(cmSPDXSnippet, SOFTWARE_SNIPPET, Snippet, snippet)
// SimpleLicensing
X_SPDX(cmSPDXLicenseExpression, SIMPLE_LICENSING_LICENSE_EXPRESSION,
LicenseExpression, licenseExpression)
X_SPDX(cmSPDXSimpleLicensingText, SIMPLE_LICENSING_SIMPLE_LICENSING_TEXT,
SimpleLicensingText, simpleLicensingText)
#undef X_SPDX

270
Source/cmSbomObject.h Normal file
View File

@@ -0,0 +1,270 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <cm/type_traits>
#include "cmSbomSerializer.h"
class cmSbomObject;
template <typename T>
inline void SerializeDispatch(T const& t, cmSbomSerializer& serializer)
{
serializer.BeginObject();
t.Serialize(serializer);
serializer.EndObject();
}
template <typename T>
inline void SerializeDispatch(T* p, cmSbomSerializer& serializer)
{
if (p->SpdxId) {
serializer.AddReference(*p->SpdxId);
}
}
inline void SerializeDispatch(std::nullptr_t, cmSbomSerializer&)
{
}
struct ObjectInterface
{
virtual ObjectInterface* Copy(void* storage) const = 0;
virtual ObjectInterface* Move(void* storage) = 0;
virtual void* Addr() noexcept = 0;
virtual void const* Addr() const noexcept = 0;
virtual void SerializeImpl(cmSbomSerializer& os) const = 0;
virtual ~ObjectInterface() = default;
ObjectInterface() = default;
ObjectInterface(ObjectInterface const&) = default;
ObjectInterface& operator=(ObjectInterface const&) = default;
ObjectInterface(ObjectInterface&&) noexcept = default;
ObjectInterface& operator=(ObjectInterface&&) noexcept = default;
};
namespace impl {
#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20150623
using max_align_t = ::max_align_t;
#else
using max_align_t = std::max_align_t;
#endif
enum class StorageKind
{
Stack,
Heap
};
template <typename, StorageKind>
struct Storage
{
};
template <typename T>
struct Storage<T, StorageKind::Stack> : ObjectInterface
{
using ValueType = cm::decay_t<T>;
explicit Storage(T x)
: Value(std::move(x))
{
}
Storage(Storage&&) noexcept(
std::is_nothrow_move_constructible<ValueType>::value) = default;
~Storage() override = default;
void* Addr() noexcept override { return std::addressof(Value); }
void const* Addr() const noexcept override { return std::addressof(Value); }
ValueType& get() { return Value; }
ValueType const& get() const { return Value; }
private:
ValueType Value;
};
template <typename T>
struct Storage<T, StorageKind::Heap> : ObjectInterface
{
using ValueType = cm::decay_t<T>;
explicit Storage(T x)
: Ptr(new T(std::move(x)))
{
}
Storage(Storage&&) noexcept = default;
~Storage() override = default;
void* Addr() noexcept override { return Ptr.get(); }
void const* Addr() const noexcept override { return Ptr.get(); }
ValueType& get() { return *Ptr; }
ValueType const& get() const { return *Ptr; }
private:
std::unique_ptr<ValueType> Ptr;
};
template <>
struct Storage<std::nullptr_t, StorageKind::Stack> : ObjectInterface
{
explicit Storage(std::nullptr_t) {}
Storage(Storage&&) noexcept = default;
~Storage() override = default;
void* Addr() noexcept override { return nullptr; }
void const* Addr() const noexcept override { return nullptr; }
void SerializeImpl(cmSbomSerializer&) const override {}
std::nullptr_t get() { return nullptr; }
std::nullptr_t get() const { return nullptr; }
};
}
struct ObjectStorage
{
template <typename T>
using Stack = impl::Storage<T, impl::StorageKind::Stack>;
template <typename T>
using Heap = impl::Storage<T, impl::StorageKind::Heap>;
static constexpr std::size_t BufferSize = 128u;
static constexpr std::size_t Size = sizeof(Heap<std::nullptr_t>) > BufferSize
? sizeof(Heap<std::nullptr_t>)
: BufferSize;
static constexpr std::size_t Align = alignof(impl::max_align_t);
struct Buffer
{
alignas(Align) unsigned char Data[Size];
};
template <typename Concrete>
using Model = cm::conditional_t<sizeof(Stack<Concrete>) <= Size &&
alignof(Stack<Concrete>) <= Align,
Stack<Concrete>, Heap<Concrete>>;
};
class cmSbomObject
{
public:
template <typename T>
struct Instance : ObjectStorage::Model<T>
{
using Base = ObjectStorage::Model<T>;
using Base::Base;
Instance(Instance&&) noexcept = default;
ObjectInterface* Copy(void* storage) const override
{
return ::new (storage) Instance(this->get());
}
ObjectInterface* Move(void* storage) override
{
return ::new (storage) Instance(std::move(*this));
}
void SerializeImpl(cmSbomSerializer& os) const override
{
SerializeDispatch(this->get(), os);
}
};
cmSbomObject() { ::new (Storage()) Instance<std::nullptr_t>(nullptr); }
cmSbomObject(std::nullptr_t)
: cmSbomObject()
{
}
template <
typename T, typename Decayed = cm::remove_cv_t<cm::remove_reference_t<T>>,
typename = cm::enable_if_t<!std::is_same<Decayed, cmSbomObject>::value>>
cmSbomObject(T&& x)
{
::new (Storage()) Instance<Decayed>(std::forward<T>(x));
}
cmSbomObject(cmSbomObject const& other)
{
other.Interface().Copy(Storage());
}
cmSbomObject(cmSbomObject&& other) noexcept
{
other.Interface().Move(Storage());
}
~cmSbomObject() noexcept { Interface().~ObjectInterface(); }
cmSbomObject& operator=(cmSbomObject rhs)
{
Interface().~ObjectInterface();
rhs.Interface().Move(Storage());
return *this;
}
bool IsNull() const noexcept { return Interface().Addr() == nullptr; }
explicit operator bool() const noexcept { return !IsNull(); }
void Serialize(cmSbomSerializer& os) const { Interface().SerializeImpl(os); }
template <typename T>
T& CastUnchecked()
{
return *static_cast<T*>(Interface().Addr());
}
template <typename T>
T const& CastUnchecked() const
{
return *static_cast<T const*>(Interface().Addr());
}
ObjectInterface& Interface()
{
return *static_cast<ObjectInterface*>(Storage());
}
ObjectInterface const& Interface() const
{
return *static_cast<ObjectInterface const*>(Storage());
}
void* Storage() { return &Data.Data; }
void const* Storage() const { return &Data.Data; }
template <typename U>
U* ptr() noexcept
{
return std::addressof(this->template CastUnchecked<U>());
}
template <typename U>
U const* ptr() const noexcept
{
return std::addressof(this->template CastUnchecked<U>());
}
private:
ObjectStorage::Buffer Data{};
};
template <typename T, typename U = cm::decay_t<T>>
U* insert_back(std::vector<cmSbomObject>& vec, T&& obj) noexcept
{
vec.emplace_back(std::forward<U>(obj));
return std::addressof(vec.back().CastUnchecked<U>());
}

38
Source/cmSbomSerializer.h Normal file
View File

@@ -0,0 +1,38 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
#include <cm/optional>
class cmSbomObject;
class cmSbomSerializer
{
public:
cmSbomSerializer() = default;
cmSbomSerializer(cmSbomSerializer const&) = default;
cmSbomSerializer(cmSbomSerializer&&) = default;
cmSbomSerializer& operator=(cmSbomSerializer const&) = default;
cmSbomSerializer& operator=(cmSbomSerializer&&) = default;
virtual void BeginObject() {}
virtual void EndObject() {}
virtual void BeginArray() {}
virtual void EndArray() {}
virtual void AddReference(std::string const& id) = 0;
virtual void AddString(std::string const& key, std::string const& value) = 0;
virtual void AddVisitable(std::string const& key,
cmSbomObject const& visitable) = 0;
virtual void AddVectorIfPresent(std::string const& key,
std::vector<cmSbomObject> const& vec) = 0;
virtual void AddVectorIfPresent(std::string const& key,
std::vector<std::string> const& vec) = 0;
virtual bool WriteSbom(std::ostream& os, cmSbomObject const& document) = 0;
virtual ~cmSbomSerializer() = default;
};

520
Source/cmSpdx.cxx Normal file
View File

@@ -0,0 +1,520 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmSpdx.h"
#include <stdexcept>
#include <string>
#include "cmSbomSerializer.h"
inline void SerializeIfPresent(cmSbomSerializer& s, std::string const& key,
cm::optional<std::string> const& v)
{
if (v) {
s.AddString(key, *v);
}
}
std::string to_string(cmSpdxIntegrityMethod::HashAlgorithmId id)
{
switch (id) {
case cmSpdxIntegrityMethod::HashAlgorithmId::ADLER32:
return "ADLER32";
case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B256:
return "BLAKE2B256";
case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B384:
return "BLAKE2B384";
case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE2B512:
return "BLAKE2B512";
case cmSpdxIntegrityMethod::HashAlgorithmId::BLAKE3:
return "BLAKE3";
case cmSpdxIntegrityMethod::HashAlgorithmId::MD2:
return "MD2";
case cmSpdxIntegrityMethod::HashAlgorithmId::MD4:
return "MD4";
case cmSpdxIntegrityMethod::HashAlgorithmId::MD5:
return "MD5";
case cmSpdxIntegrityMethod::HashAlgorithmId::MD6:
return "MD6";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA1:
return "SHA1";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA224:
return "SHA224";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA256:
return "SHA256";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA384:
return "SHA384";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA512:
return "SHA512";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_256:
return "SHA3_256";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_384:
return "SHA3_384";
case cmSpdxIntegrityMethod::HashAlgorithmId::SHA3_512:
return "SHA3_512";
}
throw std::invalid_argument("Unknown HashAlgorithmId");
}
std::string to_string(cmSpdxSoftwareArtifact::PurposeId id)
{
switch (id) {
case cmSpdxSoftwareArtifact::PurposeId::APPLICATION:
return "APPLICATION";
case cmSpdxSoftwareArtifact::PurposeId::ARCHIVE:
return "ARCHIVE";
case cmSpdxSoftwareArtifact::PurposeId::CONTAINER:
return "CONTAINER";
case cmSpdxSoftwareArtifact::PurposeId::DATA:
return "DATA";
case cmSpdxSoftwareArtifact::PurposeId::DEVICE:
return "DEVICE";
case cmSpdxSoftwareArtifact::PurposeId::FIRMWARE:
return "FIRMWARE";
case cmSpdxSoftwareArtifact::PurposeId::FILE:
return "FILE";
case cmSpdxSoftwareArtifact::PurposeId::INSTALL:
return "INSTALL";
case cmSpdxSoftwareArtifact::PurposeId::LIBRARY:
return "LIBRARY";
case cmSpdxSoftwareArtifact::PurposeId::MODULE:
return "MODULE";
case cmSpdxSoftwareArtifact::PurposeId::OPERATING_SYSTEM:
return "OPERATING_SYSTEM";
case cmSpdxSoftwareArtifact::PurposeId::SOURCE:
return "SOURCE";
}
throw std::invalid_argument("Unknown PurposeId");
}
std::string to_string(cmSpdxSbom::TypeId id)
{
switch (id) {
case cmSpdxSbom::TypeId::ANALYZED:
return "ANALYZED";
case cmSpdxSbom::TypeId::BUILD:
return "BUILD";
case cmSpdxSbom::TypeId::DEPLOYED:
return "DEPLOYED";
case cmSpdxSbom::TypeId::DESIGN:
return "DESIGN";
case cmSpdxSbom::TypeId::RUNTIME:
return "RUNTIME";
case cmSpdxSbom::TypeId::SOURCE:
return "SOURCE";
case cmSpdxSbom::TypeId::TEST:
return "TEST";
}
throw std::invalid_argument("Unknown Sbom::TypeId");
}
std::string to_string(cmSpdxFile::FileKindId id)
{
switch (id) {
case cmSpdxFile::FileKindId::DIRECTORY:
return "DIRECTORY";
case cmSpdxFile::FileKindId::FILE:
return "FILE";
}
throw std::invalid_argument("Unknown File::FileKindId");
}
std::string to_string(cmSpdxRelationship::RelationshipTypeId id)
{
switch (id) {
case cmSpdxRelationship::RelationshipTypeId::DESCRIBES:
return "DESCRIBES";
case cmSpdxRelationship::RelationshipTypeId::CONTAINS:
return "CONTAINS";
case cmSpdxRelationship::RelationshipTypeId::DEPENDS_ON:
return "DEPENDS_ON";
case cmSpdxRelationship::RelationshipTypeId::OTHER:
return "OTHER";
}
throw std::invalid_argument("Unknown RelationshipTypeId");
}
std::string to_string(cmSpdxLifecycleScopedRelationship::ScopeId id)
{
switch (id) {
case cmSpdxLifecycleScopedRelationship::ScopeId::BUILD:
return "BUILD";
case cmSpdxLifecycleScopedRelationship::ScopeId::DESIGN:
return "DESIGN";
case cmSpdxLifecycleScopedRelationship::ScopeId::RUNTIME:
return "RUNTIME";
case cmSpdxLifecycleScopedRelationship::ScopeId::TEST:
return "TEST";
}
throw std::invalid_argument("Unknown Lifecycle ScopeId");
}
std::string to_string(cmSpdxAnnotation::AnnotationTypeId id)
{
switch (id) {
case cmSpdxAnnotation::AnnotationTypeId::REVIEW:
return "REVIEW";
case cmSpdxAnnotation::AnnotationTypeId::OTHER:
return "OTHER";
}
throw std::invalid_argument("Unknown AnnotationTypeId");
}
std::string to_string(cmSpdxArtifact::SupportTypeId id)
{
switch (id) {
case cmSpdxArtifact::SupportTypeId::COMMUNITY:
return "COMMUNITY";
case cmSpdxArtifact::SupportTypeId::COMMERCIAL:
return "COMMERCIAL";
case cmSpdxArtifact::SupportTypeId::NONE:
return "NONE";
}
throw std::invalid_argument("Unknown SupportTypeId");
}
void cmSpdxExternalIdentifier::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "ExternalIdentifier");
SerializeIfPresent(serializer, "externalIdentifierType",
ExternalIdentifierType);
SerializeIfPresent(serializer, "identifier", Identifier);
SerializeIfPresent(serializer, "comment", Comment);
SerializeIfPresent(serializer, "identifierLocation", IdentifierLocation);
SerializeIfPresent(serializer, "issuingAuthority", IssuingAuthority);
}
void cmSpdxExternalRef::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "ExternalRef");
SerializeIfPresent(serializer, "externalRefType", ExternalRefType);
SerializeIfPresent(serializer, "locator", Locator);
SerializeIfPresent(serializer, "contentType", ContentType);
SerializeIfPresent(serializer, "comment", Comment);
}
void cmSpdxChecksum::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "Checksum");
serializer.AddString("algorithm", to_string(Algorithm));
serializer.AddString("checksumValue", ChecksumValue);
}
void cmSpdxCreationInfo::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "CreationInfo");
SerializeIfPresent(serializer, "@id", SpdxId);
if (SpecVersion) {
serializer.AddString("specVersion", *SpecVersion);
}
SerializeIfPresent(serializer, "comment", Comment);
SerializeIfPresent(serializer, "created", Created);
if (!CreatedBy.empty()) {
serializer.AddVectorIfPresent("createdBy", CreatedBy);
}
if (!CreatedUsing.empty()) {
serializer.AddVectorIfPresent("createdUsing", CreatedUsing);
}
}
void cmSpdxIntegrityMethod::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "IntegrityMethod");
SerializeIfPresent(serializer, "comment", Comment);
}
void cmSpdxElement::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "Element");
SerializeIfPresent(serializer, "@id", SpdxId);
SerializeIfPresent(serializer, "name", Name);
SerializeIfPresent(serializer, "summary", Summary);
SerializeIfPresent(serializer, "description", Description);
SerializeIfPresent(serializer, "comment", Comment);
if (CreationInfo) {
serializer.AddVisitable("creationInfo", *CreationInfo);
}
if (VerifiedUsing) {
serializer.AddVisitable("verifiedUsing", *VerifiedUsing);
}
if (!ExternalRef.empty()) {
serializer.AddVectorIfPresent("externalRef", ExternalRef);
}
if (!ExternalIdentifier.empty()) {
serializer.AddVectorIfPresent("externalIdentifier", ExternalIdentifier);
}
if (Extension) {
serializer.AddVisitable("extension", *Extension);
}
}
void cmSpdxTool::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "Tool");
}
void cmSpdxAgent::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "Agent");
}
void cmSpdxOrganization::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxAgent::Serialize(serializer);
serializer.AddString("type", "Organization");
}
void cmSpdxPerson::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxAgent::Serialize(serializer);
serializer.AddString("type", "Person");
}
void cmSpdxSoftwareAgent::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxAgent::Serialize(serializer);
serializer.AddString("type", "SoftwareAgent");
}
void cmSpdxPositiveIntegerRange::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "PositiveIntegerRange");
SerializeIfPresent(serializer, "beginIntegerRange", BeginIntegerRange);
SerializeIfPresent(serializer, "endIntegerRange", EndIntegerRange);
}
void cmSpdxRelationship::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "Relationship");
if (From) {
serializer.AddVisitable("from", *From);
}
if (!To.empty()) {
serializer.AddVectorIfPresent("to", To);
}
if (RelationshipType) {
serializer.AddString("relationshipType", to_string(*RelationshipType));
}
SerializeIfPresent(serializer, "startTime", StartTime);
SerializeIfPresent(serializer, "endTime", EndTime);
}
void cmSpdxLifecycleScopedRelationship::Serialize(
cmSbomSerializer& serializer) const
{
cmSpdxRelationship::Serialize(serializer);
serializer.AddString("type", "LifecycleScopedRelationship");
if (Scope) {
serializer.AddString("scope", to_string(*Scope));
}
}
void cmSpdxArtifact::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "Artifact");
if (!OriginatedBy.empty()) {
serializer.AddVectorIfPresent("originatedBy", OriginatedBy);
}
if (SuppliedBy) {
serializer.AddVisitable("suppliedBy", *SuppliedBy);
}
SerializeIfPresent(serializer, "builtTime", BuiltTime);
SerializeIfPresent(serializer, "releaseTime", ReleaseTime);
SerializeIfPresent(serializer, "validUntilTime", ValidUntilTime);
SerializeIfPresent(serializer, "standardName", StandardName);
if (Support) {
serializer.AddString("support", to_string(*Support));
}
}
void cmSpdxIndividualElement::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "IndividualElement");
}
void cmSpdxAnnotation::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "Annotation");
if (AnnotationType) {
serializer.AddString("annotationType", to_string(*AnnotationType));
}
SerializeIfPresent(serializer, "contentType", ContentType);
SerializeIfPresent(serializer, "statement", Statement);
if (Element) {
serializer.AddVisitable("element", *Element);
}
}
void cmSpdxExternalMap::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "ExternalMap");
SerializeIfPresent(serializer, "externalSpdxId", ExternalSpdxId);
if (VerifiedUsing) {
serializer.AddVisitable("verifiedUsing", *VerifiedUsing);
}
SerializeIfPresent(serializer, "locationHistory", LocationHistory);
if (DefiningArtifact) {
serializer.AddVisitable("definingArtifact", *DefiningArtifact);
}
}
void cmSpdxNamespaceMap::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "NamespaceMap");
SerializeIfPresent(serializer, "prefix", Prefix);
SerializeIfPresent(serializer, "namespace", Namespace);
}
void cmSpdxElementCollection::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElement::Serialize(serializer);
serializer.AddString("type", "ElementCollection");
if (!Elements.empty()) {
serializer.AddVectorIfPresent("element", Elements);
}
if (!RootElements.empty()) {
serializer.AddVectorIfPresent("rootElement", RootElements);
}
if (!ProfileConformance.empty()) {
serializer.AddVectorIfPresent("profileConformance", ProfileConformance);
}
}
void cmSpdxPackageVerificationCode::Serialize(
cmSbomSerializer& serializer) const
{
serializer.AddString("type", "PackageVerificationCode");
if (Algorithm) {
serializer.AddString("algorithm", to_string(*Algorithm));
}
SerializeIfPresent(serializer, "hashValue", HashValue);
SerializeIfPresent(serializer, "packageVerificationCodeExcludedFile",
PackageVerificationCodeExcludedFile);
}
void cmSpdxHash::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString("type", "Hash");
serializer.AddString("hashAlgorithm", to_string(HashAlgorithm));
serializer.AddString("hashValue", HashValue);
}
void cmSpdxBundle::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElementCollection::Serialize(serializer);
serializer.AddString("type", "Bundle");
SerializeIfPresent(serializer, "context", Context);
}
void cmSpdxBom::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxBundle::Serialize(serializer);
serializer.AddString("type", "Bom");
}
void cmSpdxSbom::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxBom::Serialize(serializer);
serializer.AddString("type", "software_Sbom");
if (Types) {
for (auto const& t : *Types) {
serializer.AddString("sbomType", to_string(t));
}
}
if (LifecycleScope) {
serializer.AddString("lifecycleScope", to_string(*LifecycleScope));
}
}
void cmSpdxSoftwareArtifact::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxArtifact::Serialize(serializer);
serializer.AddString("type", "software_SoftwareArtifact");
if (PrimaryPurpose) {
serializer.AddString("primaryPurpose", to_string(*PrimaryPurpose));
}
if (AdditionalPurpose) {
for (auto const& p : *AdditionalPurpose) {
serializer.AddString("additionalPurpose", to_string(p));
}
}
SerializeIfPresent(serializer, "copyrightText", CopyrightText);
SerializeIfPresent(serializer, "attributionText", AttributionText);
if (ContentIdentifier) {
serializer.AddVisitable("contentIdentifier", *ContentIdentifier);
}
SerializeIfPresent(serializer, "artifactSize", ArtifactSize);
}
void cmSpdxPackage::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxSoftwareArtifact::Serialize(serializer);
serializer.AddString("type", "software_Package");
SerializeIfPresent(serializer, "downloadLocation", DownloadLocation);
SerializeIfPresent(serializer, "homePage", Homepage);
SerializeIfPresent(serializer, "packageVersion", PackageVersion);
SerializeIfPresent(serializer, "packageUrl", PackageUrl);
SerializeIfPresent(serializer, "sourceInfo", SourceInfo);
}
void cmSpdxFile::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxArtifact::Serialize(serializer);
serializer.AddString("type", "software_File");
if (ContentType) {
serializer.AddString("contentType", *ContentType);
}
if (FileType) {
serializer.AddString("fileType", to_string(*FileType));
}
}
void cmSpdxContentIdentifier::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxIntegrityMethod::Serialize(serializer);
serializer.AddString("type", "software_ContentIdentifier");
SerializeIfPresent(serializer, "contentIdentifierType",
ContentIdentifierType);
SerializeIfPresent(serializer, "contentValue", ContentValue);
}
void cmSpdxSnippet::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxSoftwareArtifact::Serialize(serializer);
serializer.AddString("type", "software_Snippet");
SerializeIfPresent(serializer, "byteRange", ByteRange);
SerializeIfPresent(serializer, "lineRange", LineRange);
if (SnippetFromFile) {
serializer.AddVisitable("snippetFromFile", *SnippetFromFile);
}
}
void cmSpdxDocument::Serialize(cmSbomSerializer& serializer) const
{
cmSpdxElementCollection::Serialize(serializer);
serializer.AddString("type", "SpdxDocument");
if (ExternalMap) {
serializer.AddVisitable("externalMap", *ExternalMap);
}
if (NamespaceMap) {
serializer.AddVisitable("namespaceMap", *NamespaceMap);
}
SerializeIfPresent(serializer, "dataLicense", DataLicense);
}
void cmSbomDocument::Serialize(cmSbomSerializer& serializer) const
{
serializer.AddString(
"@context",
Context.value_or("https://spdx.org/rdf/3.0.1/spdx-context.jsonld"));
if (!Graph.empty()) {
serializer.AddVectorIfPresent("@graph", Graph);
}
}

390
Source/cmSpdx.h Normal file
View File

@@ -0,0 +1,390 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
#include <cm/optional>
#include "cmSbomObject.h"
class cmSbomSerializer;
using Datetime = std::string;
using MediaType = std::string;
using SemVer = std::string;
struct cmSpdxExternalIdentifier
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> ExternalIdentifierType;
cm::optional<std::string> Identifier;
cm::optional<std::string> Comment;
cm::optional<std::string> IdentifierLocation;
cm::optional<std::string> IssuingAuthority;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxExternalRef
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> ExternalRefType;
cm::optional<std::string> Locator;
cm::optional<std::string> ContentType;
cm::optional<std::string> Comment;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxCreationInfo
{
cm::optional<std::string> SpdxId;
cm::optional<SemVer> SpecVersion;
cm::optional<std::string> Comment;
cm::optional<Datetime> Created;
std::vector<cmSbomObject> CreatedBy;
std::vector<cmSbomObject> CreatedUsing;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxIntegrityMethod
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> Comment;
enum HashAlgorithmId
{
ADLER32,
BLAKE2B256,
BLAKE2B384,
BLAKE2B512,
BLAKE3,
MD2,
MD4,
MD5,
MD6,
SHA1,
SHA224,
SHA256,
SHA384,
SHA512,
SHA3_256,
SHA3_384,
SHA3_512,
};
cmSpdxIntegrityMethod() = default;
cmSpdxIntegrityMethod(cmSpdxIntegrityMethod const&) = default;
cmSpdxIntegrityMethod(cmSpdxIntegrityMethod&&) = default;
cmSpdxIntegrityMethod& operator=(cmSpdxIntegrityMethod const&) = default;
cmSpdxIntegrityMethod& operator=(cmSpdxIntegrityMethod&&) = default;
virtual void Serialize(cmSbomSerializer&) const;
virtual ~cmSpdxIntegrityMethod() = default;
};
struct cmSpdxChecksum
{
cm::optional<std::string> SpdxId;
cmSpdxIntegrityMethod::HashAlgorithmId Algorithm;
std::string ChecksumValue;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxElement
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> Name;
cm::optional<std::string> Summary;
cm::optional<std::string> Description;
cm::optional<std::string> Comment;
cm::optional<cmSbomObject> CreationInfo;
cm::optional<cmSbomObject> VerifiedUsing;
std::vector<cmSbomObject> ExternalRef;
std::vector<cmSbomObject> ExternalIdentifier;
cm::optional<cmSbomObject> Extension;
cmSpdxElement() = default;
cmSpdxElement(cmSpdxElement const&) = default;
cmSpdxElement(cmSpdxElement&&) = default;
cmSpdxElement& operator=(cmSpdxElement const&) = default;
cmSpdxElement& operator=(cmSpdxElement&&) = default;
virtual ~cmSpdxElement() = default;
virtual void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxTool final : cmSpdxElement
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxAgent : cmSpdxElement
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxOrganization final : cmSpdxAgent
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxPerson final : cmSpdxAgent
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxSoftwareAgent final : cmSpdxAgent
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxPositiveIntegerRange
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> BeginIntegerRange;
cm::optional<std::string> EndIntegerRange;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxRelationship : cmSpdxElement
{
enum RelationshipTypeId
{
DESCRIBES,
CONTAINS,
DEPENDS_ON,
OTHER
};
cm::optional<cmSbomObject> From;
std::vector<cmSbomObject> To;
cm::optional<RelationshipTypeId> RelationshipType;
cm::optional<Datetime> StartTime;
cm::optional<Datetime> EndTime;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxLifecycleScopedRelationship final : cmSpdxRelationship
{
enum ScopeId
{
BUILD,
DESIGN,
RUNTIME,
TEST
};
cm::optional<ScopeId> Scope;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxArtifact : cmSpdxElement
{
enum SupportTypeId
{
COMMUNITY,
COMMERCIAL,
NONE
};
std::vector<cmSbomObject> OriginatedBy;
cm::optional<cmSbomObject> SuppliedBy;
cm::optional<Datetime> BuiltTime;
cm::optional<Datetime> ReleaseTime;
cm::optional<Datetime> ValidUntilTime;
cm::optional<std::string> StandardName;
cm::optional<SupportTypeId> Support;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxIndividualElement final : cmSpdxElement
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxAnnotation final : cmSpdxElement
{
enum AnnotationTypeId
{
REVIEW,
OTHER
};
cm::optional<AnnotationTypeId> AnnotationType;
cm::optional<MediaType> ContentType;
cm::optional<std::string> Statement;
cm::optional<cmSbomObject> Element;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxExternalMap
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> ExternalSpdxId;
cm::optional<cmSbomObject> VerifiedUsing;
cm::optional<std::string> LocationHistory;
cm::optional<cmSbomObject> DefiningArtifact;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxNamespaceMap
{
cm::optional<std::string> SpdxId;
cm::optional<std::string> Prefix;
cm::optional<std::string> Namespace;
void Serialize(cmSbomSerializer&) const;
};
struct cmSpdxElementCollection : cmSpdxElement
{
std::vector<cmSbomObject> Elements;
std::vector<cmSbomObject> RootElements;
std::vector<std::string> ProfileConformance;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxPackageVerificationCode final : cmSpdxIntegrityMethod
{
cm::optional<HashAlgorithmId> Algorithm;
cm::optional<std::string> HashValue;
cm::optional<std::string> PackageVerificationCodeExcludedFile;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxHash final : cmSpdxIntegrityMethod
{
HashAlgorithmId HashAlgorithm;
std::string HashValue;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxBundle : cmSpdxElementCollection
{
cm::optional<std::string> Context;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxBom : cmSpdxBundle
{
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxSbom final : cmSpdxBom
{
enum TypeId
{
ANALYZED,
BUILD,
DEPLOYED,
DESIGN,
RUNTIME,
SOURCE,
TEST
};
cm::optional<std::vector<TypeId>> Types;
cm::optional<TypeId> LifecycleScope;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxSoftwareArtifact : cmSpdxArtifact
{
enum PurposeId
{
APPLICATION,
ARCHIVE,
CONTAINER,
DATA,
DEVICE,
FIRMWARE,
FILE,
INSTALL,
LIBRARY,
MODULE,
OPERATING_SYSTEM,
SOURCE
};
cm::optional<PurposeId> PrimaryPurpose;
cm::optional<std::vector<PurposeId>> AdditionalPurpose;
cm::optional<std::string> CopyrightText;
cm::optional<std::string> AttributionText;
cm::optional<cmSbomObject> ContentIdentifier;
cm::optional<std::string> ArtifactSize;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxPackage final : cmSpdxSoftwareArtifact
{
cm::optional<std::string> DownloadLocation;
cm::optional<std::string> Homepage;
cm::optional<std::string> PackageVersion;
cm::optional<std::string> PackageUrl;
cm::optional<std::string> SourceInfo;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxDocument final : cmSpdxElementCollection
{
cm::optional<cmSbomObject> ExternalMap;
cm::optional<cmSbomObject> NamespaceMap;
std::string DataLicense;
void Serialize(cmSbomSerializer& serializer) const override;
};
struct cmSpdxContentIdentifier final : cmSpdxIntegrityMethod
{
cm::optional<std::string> ContentIdentifierType;
cm::optional<std::string> ContentValue;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxFile final : cmSpdxArtifact
{
enum FileKindId
{
DIRECTORY,
FILE
};
cm::optional<MediaType> ContentType;
cm::optional<FileKindId> FileType;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSpdxSnippet final : cmSpdxSoftwareArtifact
{
cm::optional<std::string> ByteRange;
cm::optional<std::string> LineRange;
cm::optional<cmSbomObject> SnippetFromFile;
void Serialize(cmSbomSerializer&) const override;
};
struct cmSbomDocument
{
cm::optional<std::string> Context;
std::vector<cmSbomObject> Graph;
void Serialize(cmSbomSerializer&) const;
};

119
Source/cmSpdxSerializer.cxx Normal file
View File

@@ -0,0 +1,119 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmSpdxSerializer.h"
#include <utility>
#include <cm3p/json/writer.h>
#include "cmSbomObject.h"
cmSpdxSerializer::cmSpdxSerializer()
{
Json::StreamWriterBuilder builder = Json::StreamWriterBuilder();
builder["indentation"] = " ";
Writer.reset(builder.newStreamWriter());
}
void cmSpdxSerializer::BeginObject()
{
CurrentValue = Json::objectValue;
}
void cmSpdxSerializer::BeginArray()
{
CurrentValue = Json::arrayValue;
}
void cmSpdxSerializer::EndObject()
{
}
void cmSpdxSerializer::EndArray()
{
}
void cmSpdxSerializer::AddReference(std::string const& id)
{
CurrentValue = id;
}
void cmSpdxSerializer::AddString(std::string const& key,
std::string const& value)
{
if (!value.empty()) {
CurrentValue[key] = value;
}
}
void cmSpdxSerializer::AddVisitable(std::string const& key,
cmSbomObject const& visitable)
{
if (visitable.IsNull()) {
return;
}
Json::Value parentValue = std::move(CurrentValue);
visitable.Serialize(*this);
Json::Value childValue = std::move(CurrentValue);
CurrentValue = std::move(parentValue);
CurrentValue[key] = std::move(childValue);
}
void cmSpdxSerializer::AddVectorIfPresent(std::string const& key,
std::vector<cmSbomObject> const& vec)
{
if (vec.empty()) {
return;
}
Json::Value parentValue = std::move(CurrentValue);
Json::Value childValue(Json::arrayValue);
for (auto const& item : vec) {
if (item.IsNull()) {
continue;
}
item.Serialize(*this);
if (!CurrentValue.isNull()) {
childValue.append(std::move(CurrentValue));
}
}
CurrentValue = std::move(parentValue);
CurrentValue[key] = std::move(childValue);
}
void cmSpdxSerializer::AddVectorIfPresent(std::string const& key,
std::vector<std::string> const& vec)
{
if (vec.empty()) {
return;
}
Json::Value parentValue = std::move(CurrentValue);
Json::Value childValue(Json::arrayValue);
for (auto const& item : vec) {
if (item.empty()) {
continue;
}
childValue.append(item);
}
CurrentValue = std::move(parentValue);
CurrentValue[key] = std::move(childValue);
}
bool cmSpdxSerializer::WriteSbom(std::ostream& os,
cmSbomObject const& document)
{
if (document.IsNull()) {
return false;
}
document.Serialize(*this);
Writer->write(CurrentValue, &os);
return os.good();
}

52
Source/cmSpdxSerializer.h Normal file
View File

@@ -0,0 +1,52 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>
#include "cmSbomSerializer.h"
class cmSbomObject;
struct cmSpdxSerializer final : cmSbomSerializer
{
cmSpdxSerializer();
~cmSpdxSerializer() override = default;
void BeginObject() override;
void BeginArray() override;
void EndObject() override;
void EndArray() override;
void AddReference(std::string const& id) override;
void AddString(std::string const& key, std::string const& value) override;
void AddVisitable(std::string const& key,
cmSbomObject const& visitable) override;
void AddVectorIfPresent(std::string const& key,
std::vector<cmSbomObject> const& vec) override;
void AddVectorIfPresent(std::string const& key,
std::vector<std::string> const& vec) override;
bool WriteSbom(std::ostream& os, cmSbomObject const& document) override;
Json::Value GetJson() { return CurrentValue; }
private:
Json::Value CurrentValue;
std::string CurrentKey;
std::unique_ptr<Json::StreamWriter> Writer;
};

View File

@@ -25,7 +25,7 @@ set(CMakeLib_TESTS
testRange.cxx
testOptional.cxx
testPathResolver.cxx
testSPDXSerializer.cxx
testSpdxSerializer.cxx
testStdIo.cxx
testString.cxx
testStringAlgorithms.cxx

View File

@@ -1,649 +0,0 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include <iostream>
#include <string>
#include <vector>
#include <cm/optional>
#include <cm/type_traits>
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include "cmSPDXSerializer.h"
namespace {
std::string const nonOptional(R"================({
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"@id": "_:contentIdentifier_0",
"contentIdentifierType": "INVALID_CONTENT_IDENTIFIER_TYPE_ID",
"contentIdentifierValue": "",
"type": "ContentIdentifier"
},
{
"@id": "_:creationInfo_0",
"created": "",
"createdBy": [],
"type": "CreationInfo"
},
{
"@id": "_:creationInfo_1",
"created": "",
"createdBy": [],
"type": "CreationInfo"
},
{
"@id": "_:dictionaryEntry_0",
"key": "",
"type": "DictionaryEntry"
},
{
"@id": "_:externalIdentifier_0",
"externalIdentifierType": "INVALID_EXTERNAL_IDENTIFIER_TYPE_ID",
"identifier": "",
"type": "ExternalIdentifier"
},
{
"@id": "_:externalMap_0",
"externalSpdxId": "",
"type": "ExternalMap"
},
{
"@id": "_:externalRef_0",
"type": "ExternalRef"
},
{
"@id": "_:hash_0",
"algorithm": "INVALID_HASH_TYPE_ID",
"hashValue": "",
"type": "Hash"
},
{
"@id": "_:namespaceMap_0",
"namespace": "",
"prefix": "",
"type": "NamespaceMap"
},
{
"@id": "_:packageVerificationCode_0",
"algorithm": "INVALID_HASH_TYPE_ID",
"hashValue": "",
"type": "PackageVerificationCode"
},
{
"@id": "_:positiveIntegerRange_0",
"beginIntegerRange": 0,
"endIntegerRange": 0,
"type": "PositiveIntegerRange"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd0",
"type": "Agent"
},
{
"annotationType": "INVALID_ANNOTATION_TYPE_ID",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd1",
"subject": "",
"type": "Annotation"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd10",
"type": "SpdxDocument"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd11",
"type": "Tool"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd12",
"type": "File"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd13",
"type": "Package"
},
{
"context": "",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd14",
"type": "Sbom"
},
{
"creationInfo": "_:creationInfo_0",
"snippetFromFile": "",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd15",
"type": "Snippet"
},
{
"creationInfo": "_:creationInfo_0",
"licenseExpression": "",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd16",
"type": "LicenseExpression"
},
{
"creationInfo": "_:creationInfo_0",
"licenseText": "",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd17",
"type": "SimpleLicensingText"
},
{
"context": "",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd2",
"type": "Bom"
},
{
"context": "",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd3",
"type": "Bundle"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd4",
"type": "IndividualElement"
},
{
"creationInfo": "_:creationInfo_0",
"from": "",
"relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd5",
"to": [],
"type": "LifecycleScopedRelationship"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd6",
"type": "Organization"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd7",
"type": "Person"
},
{
"creationInfo": "_:creationInfo_0",
"from": "",
"relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd8",
"to": [],
"type": "Relationship"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd9",
"type": "SoftwareAgent"
}
]
})================");
std::string const Optional(R"================({
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"@id": "_:contentIdentifier_0",
"contentIdentifierType": "gitoid",
"contentIdentifierValue": "ContentIdentifierValue",
"type": "ContentIdentifier"
},
{
"@id": "_:creationInfo_0",
"created": "",
"createdBy": [],
"type": "CreationInfo"
},
{
"@id": "_:creationInfo_1",
"Comment": "Comment",
"created": "Created",
"createdBy": [
"testRef"
],
"createdUsing": [
"testRef"
],
"type": "CreationInfo"
},
{
"@id": "_:dictionaryEntry_0",
"key": "Key",
"type": "DictionaryEntry",
"value": "Value"
},
{
"@id": "_:externalIdentifier_0",
"comment": "Comment",
"externalIdentifierType": "other",
"identifier": "Identifier",
"identifierLocator": [
"IdentifierLocator"
],
"issuingAuthority": "IssuingAuthority",
"type": "ExternalIdentifier"
},
{
"@id": "_:externalMap_0",
"definingArtifact": "testRef",
"externalSpdxId": "ExternalSpdxId",
"integrityMethod": [
"testRef"
],
"locationHint": "LocationHint",
"type": "ExternalMap"
},
{
"@id": "_:externalRef_0",
"comment": "Comment",
"contentType": "ContentType",
"externalRefType": "other",
"locator": [
"Locator"
],
"type": "ExternalRef"
},
{
"@id": "_:hash_0",
"algorithm": "other",
"hashValue": "HashValue",
"type": "Hash"
},
{
"@id": "_:namespaceMap_0",
"namespace": "Namespace",
"prefix": "Namespace",
"type": "NamespaceMap"
},
{
"@id": "_:packageVerificationCode_0",
"algorithm": "other",
"hashValue": "HashValue",
"type": "PackageVerificationCode"
},
{
"@id": "_:positiveIntegerRange_0",
"beginIntegerRange": 1,
"endIntegerRange": 2,
"type": "PositiveIntegerRange"
},
{
"comment": "Comment",
"creationInfo": "_:creationInfo_0",
"description": "Description",
"extension": [
"testRef"
],
"externalIdentifier": [
"testRef"
],
"externalRef": [
"testRef"
],
"name": "Name",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd0",
"summary": "Summary",
"type": "Agent",
"verifiedUsing": [
"testRef"
]
},
{
"annotationType": "other",
"contentType": "ContentType",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd1",
"statement": "Statement",
"subject": "testRef",
"type": "Annotation"
},
{
"creationInfo": "_:creationInfo_0",
"dataLicense": "testRef",
"externalMap": "testRef",
"namespaceMap": "testRef",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd10",
"type": "SpdxDocument"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd11",
"type": "Tool"
},
{
"additionalPurpose": [
"other"
],
"attributionText": "AttributionText",
"contentIdentifier": "testRef",
"contentType": "ContentType",
"copyrightText": "CopyrightText",
"creationInfo": "_:creationInfo_0",
"fileKind": "file",
"primaryPurpose": "file",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd12",
"type": "File"
},
{
"creationInfo": "_:creationInfo_0",
"downloadLocation": "DownloadLocation",
"homePage": "HomePage",
"packageUrl": "PackageUrl",
"packageVersion": "PackageVersion",
"sourceInfo": "SourceInfo",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd13",
"type": "Package"
},
{
"context": "",
"creationInfo": "_:creationInfo_0",
"sbomType": [
"build"
],
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd14",
"type": "Sbom"
},
{
"byteRange": "testRef",
"creationInfo": "_:creationInfo_0",
"lineRange": "testRef",
"snippetFromFile": "testRef",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd15",
"type": "Snippet"
},
{
"creationInfo": "_:creationInfo_0",
"customIdToUri": [
"testRef"
],
"licenseExpression": "LicenseExpression",
"licenseListVersion": "LicenseListVersion",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd16",
"type": "LicenseExpression"
},
{
"creationInfo": "_:creationInfo_0",
"licenseText": "LicenseText",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd17",
"type": "SimpleLicensingText"
},
{
"context": "Context",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd2",
"type": "Bom"
},
{
"context": "",
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd3",
"type": "Bundle"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd4",
"type": "IndividualElement"
},
{
"completeness": "noAssertion",
"creationInfo": "_:creationInfo_0",
"endTime": "EndTime",
"from": "testRef",
"relationshipType": "other",
"scope": "other",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd5",
"startTime": "StartTime",
"to": [
"testRef"
],
"type": "LifecycleScopedRelationship"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd6",
"type": "Organization"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd7",
"type": "Person"
},
{
"creationInfo": "_:creationInfo_0",
"from": "",
"relationshipType": "INVALID_RELATIONSHIP_TYPE_ID",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd8",
"to": [],
"type": "Relationship"
},
{
"creationInfo": "_:creationInfo_0",
"spdxId": "https://cmake.org/testSPDXSerialization-gnrtd9",
"type": "SoftwareAgent"
}
]
})================");
}
int testNonOptional()
{
cmSPDXSimpleGraph graph("https://cmake.org/testSPDXSerialization-gnrtd");
// Core
graph.insert<cmSPDXAgent>();
graph.insert<cmSPDXAnnotation>();
graph.insert<cmSPDXBom>();
graph.insert<cmSPDXBundle>();
graph.insert<cmSPDXCreationInfo>();
graph.insert<cmSPDXDictionaryEntry>();
graph.insert<cmSPDXExternalIdentifier>();
graph.insert<cmSPDXExternalMap>();
graph.insert<cmSPDXExternalRef>();
graph.insert<cmSPDXHash>();
graph.insert<cmSPDXIndividualElement>();
graph.insert<cmSPDXLifecycleScopedRelationship>();
graph.insert<cmSPDXNamespaceMap>();
graph.insert<cmSPDXOrganization>();
graph.insert<cmSPDXPackageVerificationCode>();
graph.insert<cmSPDXPerson>();
graph.insert<cmSPDXPositiveIntegerRange>();
graph.insert<cmSPDXRelationship>();
graph.insert<cmSPDXSoftwareAgent>();
graph.insert<cmSPDXSpdxDocument>();
graph.insert<cmSPDXTool>();
// Software
graph.insert<cmSPDXContentIdentifier>();
graph.insert<cmSPDXFile>();
graph.insert<cmSPDXPackage>();
graph.insert<cmSPDXSbom>();
graph.insert<cmSPDXSnippet>();
// SimpleLicensing
graph.insert<cmSPDXLicenseExpression>();
graph.insert<cmSPDXSimpleLicensingText>();
Json::Value root;
Json::Reader().parse(nonOptional.c_str(), root);
std::cout << "NonOptional SPDX:";
std::cout << "\nConstructed Graph: " << graph.toJsonLD().toStyledString();
std::cout << "\nComparison Graph:" << root.toStyledString() << "\n";
// Convert to string to disregard differences in number signedness
return root.toStyledString() == graph.toJsonLD().toStyledString();
};
int testOptional()
{
cmSPDXSimpleGraph graph("https://cmake.org/testSPDXSerialization-gnrtd");
cmSPDXIdentifierReference ident("testRef");
// Core
auto& agent = graph.insert<cmSPDXAgent>();
agent.Comment = "Comment";
agent.Description = "Description";
agent.Extension.emplace().push_back(ident);
agent.ExternalIdentifier.emplace().push_back(ident);
agent.ExternalRef.emplace().push_back(ident);
agent.Name = "Name";
agent.Summary = "Summary";
agent.VerifiedUsing.emplace().push_back(ident);
auto& annotation = graph.insert<cmSPDXAnnotation>();
annotation.AnnotationType = cmSPDXAnnotationType::OTHER;
annotation.ContentType = "ContentType";
annotation.Statement = "Statement";
annotation.Subject = ident;
auto& bom = graph.insert<cmSPDXBom>();
bom.Context = "Context";
graph.insert<cmSPDXBundle>();
auto& creationInfo = graph.insert<cmSPDXCreationInfo>();
creationInfo.Comment = "Comment";
creationInfo.Created = "Created";
creationInfo.CreatedBy.push_back(ident);
creationInfo.CreatedUsing.emplace().push_back(ident);
auto& dictionaryEntry = graph.insert<cmSPDXDictionaryEntry>();
dictionaryEntry.Key = "Key";
dictionaryEntry.Value = "Value";
auto& externalIdentifier = graph.insert<cmSPDXExternalIdentifier>();
externalIdentifier.Comment = "Comment";
externalIdentifier.ExternalIdentifierType =
cmSPDXExternalIdentifierType::OTHER;
externalIdentifier.Identifier = "Identifier";
externalIdentifier.IdentifierLocator.emplace().push_back(
"IdentifierLocator");
externalIdentifier.IssuingAuthority = "IssuingAuthority";
auto& externalMap = graph.insert<cmSPDXExternalMap>();
externalMap.DefiningArtifact = ident;
externalMap.ExternalSpdxId = "ExternalSpdxId";
externalMap.LocationHint = "LocationHint";
externalMap.IntegrityMethod.emplace().push_back(ident);
auto& externalRef = graph.insert<cmSPDXExternalRef>();
externalRef.Comment = "Comment";
externalRef.ContentType = "ContentType";
externalRef.ExternalRefType = cmSPDXExternalRefType::OTHER;
externalRef.Locator.emplace().push_back("Locator");
auto& hash = graph.insert<cmSPDXHash>();
hash.Algorithm = cmSPDXHashAlgorithm::OTHER;
hash.HashValue = "HashValue";
graph.insert<cmSPDXIndividualElement>();
auto& lifecycleScopedRelationship =
graph.insert<cmSPDXLifecycleScopedRelationship>();
lifecycleScopedRelationship.Completeness =
cmSPDXRelationshipCompletenessType::NO_ASSERTION;
lifecycleScopedRelationship.EndTime = "EndTime";
lifecycleScopedRelationship.From = ident;
lifecycleScopedRelationship.RelationshipType = cmSPDXRelationshipType::OTHER;
lifecycleScopedRelationship.StartTime = "StartTime";
lifecycleScopedRelationship.To.push_back(ident);
lifecycleScopedRelationship.Scope = cmSPDXLifecycleScopeType::OTHER;
auto& namespaceMap = graph.insert<cmSPDXNamespaceMap>();
namespaceMap.Namespace = "Namespace";
namespaceMap.Prefix = "Prefix";
graph.insert<cmSPDXOrganization>();
auto& packageVerificationCode =
graph.insert<cmSPDXPackageVerificationCode>();
packageVerificationCode.Algorithm = cmSPDXHashAlgorithm::OTHER;
packageVerificationCode.HashValue = "HashValue";
packageVerificationCode.PackageVerificationCodeExcludedFile.emplace()
.push_back("PacakgeVerificationCodeExcludeFile");
graph.insert<cmSPDXPerson>();
auto& positiveIntegerRange = graph.insert<cmSPDXPositiveIntegerRange>();
positiveIntegerRange.BeginIntegerRange = 1;
positiveIntegerRange.EndIntegerRange = 2;
graph.insert<cmSPDXRelationship>();
graph.insert<cmSPDXSoftwareAgent>();
auto& spdxDocument = graph.insert<cmSPDXSpdxDocument>();
spdxDocument.DataLicense = ident;
spdxDocument.ExternalMap = ident;
spdxDocument.NamespaceMap = ident;
graph.insert<cmSPDXTool>();
// Software
auto& contentIdentifier = graph.insert<cmSPDXContentIdentifier>();
contentIdentifier.ContentIdentifierType =
cmSPDXContentIdentifierType::GITOID;
contentIdentifier.ContentIdentifierValue = "ContentIdentifierValue";
auto& file = graph.insert<cmSPDXFile>();
file.AdditionalPurpose.emplace().push_back(cmSPDXSoftwarePurpose::OTHER);
file.AttributionText = "AttributionText";
file.ContentIdentifier = ident;
file.CopyrightText = "CopyrightText";
file.PrimaryPurpose = cmSPDXSoftwarePurpose::FILE;
file.ContentType = "ContentType";
file.FileKind = cmSPDXFileKindType::FILE;
auto& package = graph.insert<cmSPDXPackage>();
package.DownloadLocation = "DownloadLocation";
package.HomePage = "HomePage";
package.PackageUrl = "PackageUrl";
package.PackageVersion = "PackageVersion";
package.SourceInfo = "SourceInfo";
auto& sbom = graph.insert<cmSPDXSbom>();
sbom.SbomType.emplace().push_back(cmSPDXSbomType::BUILD);
auto& snippet = graph.insert<cmSPDXSnippet>();
snippet.ByteRange = ident;
snippet.LineRange = ident;
snippet.SnippetFromFile = ident;
// SimpleLicensing
auto& licenseExpression = graph.insert<cmSPDXLicenseExpression>();
licenseExpression.CustomIdToUri.emplace().push_back(ident);
licenseExpression.LicenseExpression = "LicenseExpression";
licenseExpression.LicenseListVersion = "LicenseListVersion";
auto& simpleLicensingText = graph.insert<cmSPDXSimpleLicensingText>();
simpleLicensingText.LicenseText = "LicenseText";
Json::Value root;
Json::Reader().parse(Optional.c_str(), root);
std::cout << "Optional SPDX:";
std::cout << "\nConstructed Graph: " << graph.toJsonLD().toStyledString();
std::cout << "\nComparison Graph:" << root.toStyledString() << "\n";
// Convert to string to disregard differences in number signedness
return root.toStyledString() == graph.toJsonLD().toStyledString();
};
int testSPDXSerializer(int /* argc */, char* /* argv */[])
{
if (!testNonOptional())
return -1;
if (!testOptional())
return -1;
return 0;
}

View File

@@ -0,0 +1,84 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include <string>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cm3p/json/value.h>
#include "cmSbomObject.h"
#include "cmSpdx.h"
#include "cmSpdxSerializer.h"
#include "testCommon.h"
namespace {
bool testSerializeSpdxJson()
{
auto constexpr contextUrl = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld";
cmSbomDocument doc;
doc.Context = contextUrl;
cmSpdxDocument spdxValue;
spdxValue.SpdxId = "_:SPDXRef-Document";
spdxValue.DataLicense = "CC0-1.0";
auto spdx = insert_back(doc.Graph, std::move(spdxValue));
{
cmSpdxCreationInfo ci;
ci.SpdxId = "_:SPDXRef-CreationInfo";
ci.SpecVersion = "3.0.1";
spdx->CreationInfo = std::move(ci);
}
{
cmSpdxPackage pkg;
pkg.Name = "sample-package";
pkg.SpdxId = "_:SPDXRef-Package";
spdx->RootElements.emplace_back(std::move(pkg));
}
{
cmSpdxSerializer serializer;
doc.Serialize(serializer);
auto value = serializer.GetJson();
ASSERT_TRUE(value.isObject());
Json::Value const& context = value["@context"];
ASSERT_EQUAL(context.asString(), contextUrl);
Json::Value const& graph = value["@graph"];
ASSERT_TRUE(graph.isArray());
Json::Value const& docValue = graph[0];
ASSERT_TRUE(docValue.isObject());
ASSERT_EQUAL(docValue["type"].asString(), "SpdxDocument");
ASSERT_EQUAL(docValue["@id"].asString(), "_:SPDXRef-Document");
auto const& creationInfo = docValue["creationInfo"];
ASSERT_EQUAL(creationInfo["@id"].asString(), "_:SPDXRef-CreationInfo");
ASSERT_EQUAL(creationInfo["type"].asString(), "CreationInfo");
auto const& package = docValue["rootElement"][0];
ASSERT_EQUAL(package["type"].asString(), "software_Package");
ASSERT_EQUAL(package["@id"].asString(), "_:SPDXRef-Package");
}
return true;
}
} // namespace
int testSpdxSerializer(int /*unused*/, char* /*unused*/[])
{
return runTests({ testSerializeSpdxJson });
}