blob: 8ee157438a2738af64e8df4ad2c9f8825b2d2fa8 [file] [log] [blame] [edit]
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmExportSbomGenerator.h"
#include <array>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cmext/algorithm>
#include "cmArgumentParserTypes.h"
#include "cmFindPackageStack.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmList.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmSbomArguments.h"
#include "cmSbomObject.h"
#include "cmSpdx.h"
#include "cmSpdxSerializer.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
cmSpdxPackage::PurposeId GetPurpose(cmStateEnums::TargetType type)
{
switch (type) {
case cmStateEnums::TargetType::EXECUTABLE:
return cmSpdxPackage::PurposeId::APPLICATION;
case cmStateEnums::TargetType::STATIC_LIBRARY:
case cmStateEnums::TargetType::SHARED_LIBRARY:
case cmStateEnums::TargetType::MODULE_LIBRARY:
case cmStateEnums::TargetType::OBJECT_LIBRARY:
case cmStateEnums::TargetType::INTERFACE_LIBRARY:
return cmSpdxPackage::PurposeId::LIBRARY;
case cmStateEnums::TargetType::UTILITY:
return cmSpdxPackage::PurposeId::SOURCE;
case cmStateEnums::TargetType::GLOBAL_TARGET:
case cmStateEnums::TargetType::UNKNOWN_LIBRARY:
default:
return cmSpdxPackage::PurposeId::ARCHIVE;
}
}
cmExportSbomGenerator::cmExportSbomGenerator(cmSbomArguments args)
: PackageName(std::move(args.PackageName))
, PackageVersion(std::move(args.Version))
, PackageDescription(std::move(args.Description))
, PackageWebsite(std::move(args.Website))
, PackageLicense(std::move(args.License))
, PackageFormat(args.GetFormat())
{
}
bool cmExportSbomGenerator::GenerateImportFile(std::ostream& os)
{
return this->GenerateMainFile(os);
}
void cmExportSbomGenerator::WriteSbom(cmSbomDocument& doc,
std::ostream& os) const
{
switch (this->PackageFormat) {
case cmSbomArguments::SbomFormat::SPDX_3_0_JSON:
cmSpdxSerializer{}.WriteSbom(os, cmSbomObject(doc));
break;
case cmSbomArguments::SbomFormat::NONE:
break;
}
}
bool cmExportSbomGenerator::AddPackageInformation(
cmSpdxPackage& artifact, std::string const& name,
cmPackageInformation const& package) const
{
if (name.empty()) {
return false;
}
cmSpdxOrganization org;
org.Name = name;
artifact.OriginatedBy.emplace_back(std::move(org));
if (package.Description) {
artifact.Description = *package.Description;
}
if (package.Version) {
artifact.PackageVersion = *package.Version;
}
if (package.PackageUrl) {
artifact.PackageUrl = *package.PackageUrl;
}
if (package.License) {
artifact.CopyrightText = *package.License;
}
artifact.BuiltTime = cmSystemTools::GetCurrentDateTime("%FT%TZ");
cmSpdxExternalRef externalRef;
externalRef.Locator = cmStrCat("cmake:find_package(", name, ")");
externalRef.ExternalRefType = "buildSystem";
return true;
}
cmSpdxDocument cmExportSbomGenerator::GenerateSbom() const
{
cmSpdxTool tool;
tool.SpdxId = "CMake#Agent";
tool.Name = "CMake";
cmSpdxCreationInfo ci;
ci.Created = cmSystemTools::GetCurrentDateTime("%FT%TZ");
ci.CreatedUsing = { tool };
ci.Comment = "This SBOM was generated from the CMakeLists.txt File";
cmSpdxDocument proj;
proj.Name = PackageName;
proj.SpdxId = cmStrCat(PackageName, "#SPDXDocument");
proj.ProfileConformance = { "core", "software" };
proj.CreationInfo = ci;
if (!this->PackageDescription.empty()) {
proj.Description = this->PackageDescription;
}
if (!this->PackageLicense.empty()) {
proj.DataLicense = this->PackageLicense;
}
return proj;
}
cmSpdxPackage cmExportSbomGenerator::GenerateImportTarget(
cmGeneratorTarget const* target) const
{
cmSpdxPackage package;
package.SpdxId = cmStrCat(target->GetName(), "#Package");
package.Name = target->GetName();
package.PrimaryPurpose = GetPurpose(target->GetType());
cmSpdxExternalRef buildSystem;
buildSystem.Locator = "CMake#Agent";
buildSystem.ExternalRefType = "buildSystem";
buildSystem.Comment = "Build System used for this target";
package.ExternalRef = { buildSystem };
if (!this->PackageVersion.empty()) {
package.PackageVersion = this->PackageVersion;
}
if (!this->PackageWebsite.empty()) {
package.Homepage = this->PackageWebsite;
}
if (!this->PackageUrl.empty()) {
package.DownloadLocation = this->PackageUrl;
}
return package;
}
void cmExportSbomGenerator::GenerateLinkProperties(
cmSbomDocument& doc, cmSpdxDocument* project, std::string const& libraries,
TargetProperties const& current,
std::vector<TargetProperties> const& allTargets) const
{
auto itProp = current.Properties.find(libraries);
if (itProp == current.Properties.end()) {
return;
}
std::map<std::string, std::vector<std::string>> allowList = { { "LINK_ONLY",
{} } };
std::string interfaceLinkLibraries;
if (!cmGeneratorExpression::ForbidGeneratorExpressions(
current.Target, itProp->first, itProp->second, interfaceLinkLibraries,
allowList)) {
return;
}
auto makeRel = [&](char const* desc) {
cmSpdxRelationship r;
r.RelationshipType = cmSpdxRelationship::RelationshipTypeId::DEPENDS_ON;
r.Description = desc;
r.From = current.Package;
return r;
};
auto linkLibraries = makeRel("Linked Libraries");
auto linkRequires = makeRel("Required Runtime Libraries");
auto buildRequires = makeRel("Required Build-Time Libraries");
auto addArtifact =
[&](std::string const& name) -> std::pair<bool, cmSpdxPackage const*> {
auto it = this->LinkTargets.find(name);
if (it != this->LinkTargets.end()) {
LinkInfo const& linkInfo = it->second;
if (linkInfo.Package.empty()) {
for (auto const& t : allTargets) {
if (t.Target->GetName() == linkInfo.Component) {
return { true, t.Package };
}
}
}
std::string pkgName =
cmStrCat(linkInfo.Package, ":", linkInfo.Component);
cmSpdxPackage pkg;
pkg.Name = pkgName;
pkg.SpdxId = cmStrCat(pkgName, "#Package");
if (!linkInfo.Package.empty()) {
auto const& pkgIt = this->Requirements.find(linkInfo.Package);
if (pkgIt != this->Requirements.end() &&
pkgIt->second.Components.count(linkInfo.Component) > 0) {
this->AddPackageInformation(pkg, pkgIt->first, pkgIt->second);
}
}
return { true, insert_back(project->Elements, std::move(pkg)) };
}
cmSpdxPackage pkg;
pkg.SpdxId = cmStrCat(name, "#Package");
pkg.Name = name;
return { false, insert_back(project->Elements, std::move(pkg)) };
};
auto handleDependencies = [&](std::vector<std::string> const& names,
cmSpdxRelationship& internalDeps,
cmSpdxRelationship& externalDeps) {
for (auto const& n : names) {
auto res = addArtifact(n);
if (!res.second) {
continue;
}
if (res.first) {
internalDeps.To.push_back(res.second);
} else {
externalDeps.To.push_back(res.second);
}
}
};
handleDependencies(allowList["LINK_ONLY"], linkLibraries, linkRequires);
handleDependencies(cmList{ interfaceLinkLibraries }, linkLibraries,
buildRequires);
if (!linkLibraries.To.empty()) {
insert_back(doc.Graph, std::move(linkLibraries));
}
if (!linkRequires.To.empty()) {
insert_back(doc.Graph, std::move(linkRequires));
}
if (!buildRequires.To.empty()) {
insert_back(doc.Graph, std::move(buildRequires));
}
}
bool cmExportSbomGenerator::GenerateProperties(
cmSbomDocument& doc, cmSpdxDocument* proj, TargetProperties const& current,
std::vector<TargetProperties> const& allTargets) const
{
this->GenerateLinkProperties(doc, proj, "LINK_LIBRARIES", current,
allTargets);
this->GenerateLinkProperties(doc, proj, "INTERFACE_LINK_LIBRARIES", current,
allTargets);
return true;
}
bool cmExportSbomGenerator::PopulateLinkLibrariesProperty(
cmGeneratorTarget const* target,
cmGeneratorExpression::PreprocessContext preprocessRule,
ImportPropertyMap& properties)
{
static std::array<std::string, 3> const linkIfaceProps = {
{ "LINK_LIBRARIES", "LINK_LIBRARIES_DIRECT",
"LINK_LIBRARIES_DIRECT_EXCLUDE" }
};
bool hadLINK_LIBRARIES = false;
for (std::string const& linkIfaceProp : linkIfaceProps) {
if (cmValue input = target->GetProperty(linkIfaceProp)) {
std::string prepro =
cmGeneratorExpression::Preprocess(*input, preprocessRule);
if (!prepro.empty()) {
this->ResolveTargetsInGeneratorExpressions(prepro, target,
ReplaceFreeTargets);
properties[linkIfaceProp] = prepro;
hadLINK_LIBRARIES = true;
}
}
}
return hadLINK_LIBRARIES;
}
bool cmExportSbomGenerator::NoteLinkedTarget(
cmGeneratorTarget const* target, std::string const& linkedName,
cmGeneratorTarget const* linkedTarget)
{
if (cm::contains(this->ExportedTargets, linkedTarget)) {
this->LinkTargets.emplace(linkedName,
LinkInfo{ "", linkedTarget->GetExportName() });
return true;
}
if (linkedTarget->IsImported()) {
using Package = cm::optional<std::pair<std::string, cmPackageInformation>>;
auto pkgInfo = [](cmTarget* t) -> Package {
cmFindPackageStack pkgStack = t->GetFindPackageStack();
if (!pkgStack.Empty()) {
return std::make_pair(pkgStack.Top().Name, pkgStack.Top().PackageInfo);
}
std::string const pkgName =
t->GetSafeProperty("EXPORT_FIND_PACKAGE_NAME");
if (pkgName.empty()) {
return cm::nullopt;
}
cmPackageInformation package;
return std::make_pair(pkgName, package);
}(linkedTarget->Target);
if (!pkgInfo) {
target->Makefile->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("Target \"", target->GetName(),
"\" references imported target \"", linkedName,
"\" which does not come from any known package."));
return false;
}
std::string const& pkgName = pkgInfo->first;
auto const& prefix = cmStrCat(pkgName, "::");
std::string component;
if (!cmHasPrefix(linkedName, prefix)) {
component = linkedName;
} else {
component = linkedName.substr(prefix.length());
}
this->LinkTargets.emplace(linkedName, LinkInfo{ pkgName, component });
cmPackageInformation& req =
this->Requirements.insert(std::move(*pkgInfo)).first->second;
req.Components.emplace(std::move(component));
return true;
}
// Target belongs to another export from this build.
auto const& exportInfo = this->FindExportInfo(linkedTarget);
if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
auto const& linkNamespace = *exportInfo.Namespaces.begin();
if (!cmHasSuffix(linkNamespace, "::")) {
target->Makefile->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("Target \"", target->GetName(), "\" references target \"",
linkedName,
"\", which does not use the standard namespace separator. "
"This is not allowed."));
}
std::string pkgName{ linkNamespace.data(), linkNamespace.size() - 2 };
std::string component = linkedTarget->GetExportName();
if (pkgName == this->GetPackageName()) {
this->LinkTargets.emplace(linkedName, LinkInfo{ "", component });
} else {
this->LinkTargets.emplace(linkedName, LinkInfo{ pkgName, component });
this->Requirements[pkgName].Components.emplace(std::move(component));
}
return true;
}
// Target belongs to multiple namespaces or multiple export sets.
// cmExportFileGenerator::HandleMissingTarget should have complained about
// this already.
return false;
}